18
18
import java .lang .annotation .Annotation ;
19
19
import java .util .Collections ;
20
20
import java .util .List ;
21
+ import java .util .Map ;
21
22
import java .util .function .Function ;
22
23
import java .util .stream .Collectors ;
23
24
36
37
import org .springframework .http .server .reactive .ServerHttpRequest ;
37
38
import org .springframework .http .server .reactive .ServerHttpResponse ;
38
39
import org .springframework .util .Assert ;
39
- import org .springframework .util .ObjectUtils ;
40
- import org .springframework .validation .BeanPropertyBindingResult ;
41
- import org .springframework .validation .Errors ;
42
- import org .springframework .validation .SmartValidator ;
43
40
import org .springframework .validation .Validator ;
44
41
import org .springframework .validation .annotation .Validated ;
42
+ import org .springframework .web .reactive .result .method .BindingContext ;
45
43
import org .springframework .web .server .ServerWebExchange ;
46
44
import org .springframework .web .server .ServerWebInputException ;
47
45
import org .springframework .web .server .UnsupportedMediaTypeStatusException ;
@@ -62,8 +60,6 @@ public abstract class AbstractMessageReaderArgumentResolver {
62
60
63
61
private final List <HttpMessageReader <?>> messageReaders ;
64
62
65
- private final Validator validator ;
66
-
67
63
private final ReactiveAdapterRegistry adapterRegistry ;
68
64
69
65
private final List <MediaType > supportedMediaTypes ;
@@ -72,26 +68,22 @@ public abstract class AbstractMessageReaderArgumentResolver {
72
68
/**
73
69
* Constructor with {@link HttpMessageReader}'s and a {@link Validator}.
74
70
* @param readers readers to convert from the request body
75
- * @param validator validator to validate decoded objects with
76
71
*/
77
- protected AbstractMessageReaderArgumentResolver (List <HttpMessageReader <?>> readers , Validator validator ) {
78
-
79
- this (readers , validator , new ReactiveAdapterRegistry ());
72
+ protected AbstractMessageReaderArgumentResolver (List <HttpMessageReader <?>> readers ) {
73
+ this (readers , new ReactiveAdapterRegistry ());
80
74
}
81
75
82
76
/**
83
77
* Constructor that also accepts a {@link ReactiveAdapterRegistry}.
84
78
* @param messageReaders readers to convert from the request body
85
- * @param validator validator to validate decoded objects with
86
79
* @param adapterRegistry for adapting to other reactive types from Flux and Mono
87
80
*/
88
81
protected AbstractMessageReaderArgumentResolver (List <HttpMessageReader <?>> messageReaders ,
89
- Validator validator , ReactiveAdapterRegistry adapterRegistry ) {
82
+ ReactiveAdapterRegistry adapterRegistry ) {
90
83
91
84
Assert .notEmpty (messageReaders , "At least one HttpMessageReader is required." );
92
85
Assert .notNull (adapterRegistry , "'adapterRegistry' is required" );
93
86
this .messageReaders = messageReaders ;
94
- this .validator = validator ;
95
87
this .adapterRegistry = adapterRegistry ;
96
88
this .supportedMediaTypes = messageReaders .stream ()
97
89
.flatMap (converter -> converter .getReadableMediaTypes ().stream ())
@@ -115,7 +107,7 @@ public ReactiveAdapterRegistry getAdapterRegistry() {
115
107
116
108
117
109
protected Mono <Object > readBody (MethodParameter bodyParameter , boolean isBodyRequired ,
118
- ServerWebExchange exchange ) {
110
+ BindingContext bindingContext , ServerWebExchange exchange ) {
119
111
120
112
ResolvableType bodyType = ResolvableType .forMethodParameter (bodyParameter );
121
113
ReactiveAdapter adapter = getAdapterRegistry ().getAdapterTo (bodyType .resolve ());
@@ -135,32 +127,42 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
135
127
for (HttpMessageReader <?> reader : getMessageReaders ()) {
136
128
137
129
if (reader .canRead (elementType , mediaType )) {
138
-
130
+ Map < String , Object > readHints = Collections . emptyMap ();
139
131
if (adapter != null && adapter .getDescriptor ().isMultiValue ()) {
140
- Flux <?> flux = (reader instanceof ServerHttpMessageReader ?
141
- ((ServerHttpMessageReader <?>)reader ).read (bodyType , elementType ,
142
- request , response , Collections .emptyMap ()) :
143
- reader .read (elementType , request , Collections .emptyMap ())
144
- .onErrorResumeWith (ex -> Flux .error (getReadError (ex , bodyParameter ))));
132
+ Flux <?> flux ;
133
+ if (reader instanceof ServerHttpMessageReader ) {
134
+ ServerHttpMessageReader <?> serverReader = ((ServerHttpMessageReader <?>) reader );
135
+ flux = serverReader .read (bodyType , elementType , request , response , readHints );
136
+ }
137
+ else {
138
+ flux = reader .read (elementType , request , readHints );
139
+ }
140
+ flux = flux .onErrorResumeWith (ex -> Flux .error (wrapReadError (ex , bodyParameter )));
145
141
if (checkRequired (adapter , isBodyRequired )) {
146
142
flux = flux .switchIfEmpty (Flux .error (getRequiredBodyError (bodyParameter )));
147
143
}
148
- if (this .validator != null ) {
149
- flux = flux .map (applyValidationIfApplicable (bodyParameter ));
144
+ Object [] hints = extractValidationHints (bodyParameter );
145
+ if (hints != null ) {
146
+ flux = flux .concatMap (getValidator (hints , bodyParameter , bindingContext , exchange ));
150
147
}
151
148
return Mono .just (adapter .fromPublisher (flux ));
152
149
}
153
150
else {
154
- Mono <?> mono = (reader instanceof ServerHttpMessageReader ?
155
- ((ServerHttpMessageReader <?>)reader ).readMono (bodyType , elementType ,
156
- request , response , Collections .emptyMap ()) :
157
- reader .readMono (elementType , request , Collections .emptyMap ())
158
- .otherwise (ex -> Mono .error (getReadError (ex , bodyParameter ))));
151
+ Mono <?> mono ;
152
+ if (reader instanceof ServerHttpMessageReader ) {
153
+ ServerHttpMessageReader <?> serverReader = (ServerHttpMessageReader <?>) reader ;
154
+ mono = serverReader .readMono (bodyType , elementType , request , response , readHints );
155
+ }
156
+ else {
157
+ mono = reader .readMono (elementType , request , readHints );
158
+ }
159
+ mono = mono .otherwise (ex -> Mono .error (wrapReadError (ex , bodyParameter )));
159
160
if (checkRequired (adapter , isBodyRequired )) {
160
161
mono = mono .otherwiseIfEmpty (Mono .error (getRequiredBodyError (bodyParameter )));
161
162
}
162
- if (this .validator != null ) {
163
- mono = mono .map (applyValidationIfApplicable (bodyParameter ));
163
+ Object [] hints = extractValidationHints (bodyParameter );
164
+ if (hints != null ) {
165
+ mono = mono .then (getValidator (hints , bodyParameter , bindingContext , exchange ));
164
166
}
165
167
if (adapter != null ) {
166
168
return Mono .just (adapter .fromPublisher (mono ));
@@ -175,50 +177,49 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
175
177
return Mono .error (new UnsupportedMediaTypeStatusException (mediaType , this .supportedMediaTypes ));
176
178
}
177
179
178
- protected boolean checkRequired ( ReactiveAdapter adapter , boolean isBodyRequired ) {
179
- return adapter != null && ! adapter . getDescriptor (). supportsEmpty () || isBodyRequired ;
180
+ protected ServerWebInputException wrapReadError ( Throwable ex , MethodParameter parameter ) {
181
+ return new ServerWebInputException ( "Failed to read HTTP message" , parameter , ex ) ;
180
182
}
181
183
182
- protected ServerWebInputException getReadError ( Throwable ex , MethodParameter parameter ) {
183
- return new ServerWebInputException ( "Failed to read HTTP message" , parameter , ex ) ;
184
+ protected boolean checkRequired ( ReactiveAdapter adapter , boolean isBodyRequired ) {
185
+ return adapter != null && ! adapter . getDescriptor (). supportsEmpty () || isBodyRequired ;
184
186
}
185
187
186
188
protected ServerWebInputException getRequiredBodyError (MethodParameter parameter ) {
187
189
return new ServerWebInputException ("Required request body is missing: " +
188
190
parameter .getMethod ().toGenericString ());
189
191
}
190
192
191
- protected <T > Function <T , T > applyValidationIfApplicable (MethodParameter methodParam ) {
192
- Annotation [] annotations = methodParam .getParameterAnnotations ();
193
+ /**
194
+ * Check if the given MethodParameter requires validation and if so return
195
+ * a (possibly empty) Object[] with validation hints. A return value of
196
+ * {@code null} indicates that validation is not required.
197
+ */
198
+ protected Object [] extractValidationHints (MethodParameter parameter ) {
199
+ Annotation [] annotations = parameter .getParameterAnnotations ();
193
200
for (Annotation ann : annotations ) {
194
201
Validated validAnnot = AnnotationUtils .getAnnotation (ann , Validated .class );
195
202
if (validAnnot != null || ann .annotationType ().getSimpleName ().startsWith ("Valid" )) {
196
203
Object hints = (validAnnot != null ? validAnnot .value () : AnnotationUtils .getValue (ann ));
197
- Object [] validHints = (hints instanceof Object [] ? (Object []) hints : new Object [] {hints });
198
- return element -> {
199
- doValidate (element , validHints , methodParam );
200
- return element ;
201
- };
204
+ return (hints instanceof Object [] ? (Object []) hints : new Object [] {hints });
202
205
}
203
206
}
204
- return element -> element ;
207
+ return null ;
205
208
}
206
209
207
- /**
208
- * TODO: replace with use of DataBinder
209
- */
210
- private void doValidate (Object target , Object [] validationHints , MethodParameter methodParam ) {
211
- String name = Conventions .getVariableNameForParameter (methodParam );
212
- Errors errors = new BeanPropertyBindingResult (target , name );
213
- if (!ObjectUtils .isEmpty (validationHints ) && this .validator instanceof SmartValidator ) {
214
- ((SmartValidator ) this .validator ).validate (target , errors , validationHints );
215
- }
216
- else if (this .validator != null ) {
217
- this .validator .validate (target , errors );
218
- }
219
- if (errors .hasErrors ()) {
220
- throw new ServerWebInputException ("Validation failed" , methodParam );
221
- }
210
+ protected <T > Function <T , Mono <T >> getValidator (Object [] validationHints ,
211
+ MethodParameter param , BindingContext binding , ServerWebExchange exchange ) {
212
+
213
+ String name = Conventions .getVariableNameForParameter (param );
214
+
215
+ return target -> binding .createBinder (exchange , target , name )
216
+ .map (binder -> {
217
+ binder .validate (validationHints );
218
+ if (binder .getBindingResult ().hasErrors ()) {
219
+ throw new ServerWebInputException ("Validation failed" , param );
220
+ }
221
+ return target ;
222
+ });
222
223
}
223
224
224
225
}
0 commit comments