|
17 | 17 | package org.springframework.validation.beanvalidation;
|
18 | 18 |
|
19 | 19 | import java.lang.reflect.Method;
|
| 20 | +import java.lang.reflect.Parameter; |
| 21 | +import java.util.Collections; |
| 22 | +import java.util.List; |
20 | 23 | import java.util.Set;
|
21 | 24 | import java.util.function.Supplier;
|
22 | 25 |
|
23 | 26 | import jakarta.validation.ConstraintViolation;
|
24 | 27 | import jakarta.validation.ConstraintViolationException;
|
| 28 | +import jakarta.validation.Valid; |
25 | 29 | import jakarta.validation.Validator;
|
26 | 30 | import jakarta.validation.ValidatorFactory;
|
27 | 31 | import org.aopalliance.intercept.MethodInterceptor;
|
28 | 32 | import org.aopalliance.intercept.MethodInvocation;
|
| 33 | +import reactor.core.publisher.Flux; |
| 34 | +import reactor.core.publisher.Mono; |
29 | 35 |
|
30 | 36 | import org.springframework.aop.ProxyMethodInvocation;
|
31 | 37 | import org.springframework.beans.factory.FactoryBean;
|
32 | 38 | import org.springframework.beans.factory.SmartFactoryBean;
|
| 39 | +import org.springframework.core.MethodParameter; |
| 40 | +import org.springframework.core.ReactiveAdapter; |
| 41 | +import org.springframework.core.ReactiveAdapterRegistry; |
| 42 | +import org.springframework.core.annotation.AnnotationUtils; |
33 | 43 | import org.springframework.lang.Nullable;
|
34 | 44 | import org.springframework.util.Assert;
|
35 | 45 | import org.springframework.util.ClassUtils;
|
| 46 | +import org.springframework.validation.BeanPropertyBindingResult; |
| 47 | +import org.springframework.validation.Errors; |
36 | 48 | import org.springframework.validation.annotation.Validated;
|
37 | 49 | import org.springframework.validation.method.MethodValidationException;
|
38 | 50 | import org.springframework.validation.method.MethodValidationResult;
|
| 51 | +import org.springframework.validation.method.ParameterErrors; |
| 52 | +import org.springframework.validation.method.ParameterValidationResult; |
39 | 53 |
|
40 | 54 | /**
|
41 | 55 | * An AOP Alliance {@link MethodInterceptor} implementation that delegates to a
|
|
65 | 79 | */
|
66 | 80 | public class MethodValidationInterceptor implements MethodInterceptor {
|
67 | 81 |
|
| 82 | + private static final boolean REACTOR_PRESENT = |
| 83 | + ClassUtils.isPresent("reactor.core.publisher.Mono", MethodValidationInterceptor.class.getClassLoader()); |
| 84 | + |
| 85 | + |
68 | 86 | private final MethodValidationAdapter validationAdapter;
|
69 | 87 |
|
70 | 88 | private final boolean adaptViolations;
|
@@ -135,6 +153,12 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
|
135 | 153 | Object[] arguments = invocation.getArguments();
|
136 | 154 | Class<?>[] groups = determineValidationGroups(invocation);
|
137 | 155 |
|
| 156 | + if (REACTOR_PRESENT) { |
| 157 | + arguments = ReactorValidationHelper.insertAsyncValidation( |
| 158 | + this.validationAdapter.getSpringValidatorAdapter(), this.adaptViolations, |
| 159 | + target, method, arguments); |
| 160 | + } |
| 161 | + |
138 | 162 | Set<ConstraintViolation<Object>> violations;
|
139 | 163 |
|
140 | 164 | if (this.adaptViolations) {
|
@@ -206,4 +230,78 @@ protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
|
206 | 230 | return this.validationAdapter.determineValidationGroups(target, invocation.getMethod());
|
207 | 231 | }
|
208 | 232 |
|
| 233 | + |
| 234 | + /** |
| 235 | + * Helper class to decorate reactive arguments with async validation. |
| 236 | + */ |
| 237 | + private final static class ReactorValidationHelper { |
| 238 | + |
| 239 | + private static final ReactiveAdapterRegistry reactiveAdapterRegistry = |
| 240 | + ReactiveAdapterRegistry.getSharedInstance(); |
| 241 | + |
| 242 | + |
| 243 | + public static Object[] insertAsyncValidation( |
| 244 | + Supplier<SpringValidatorAdapter> validatorAdapterSupplier, boolean adaptViolations, |
| 245 | + Object target, Method method, Object[] arguments) { |
| 246 | + |
| 247 | + for (int i = 0; i < method.getParameterCount(); i++) { |
| 248 | + if (arguments[i] == null) { |
| 249 | + continue; |
| 250 | + } |
| 251 | + Class<?> parameterType = method.getParameterTypes()[i]; |
| 252 | + ReactiveAdapter reactiveAdapter = reactiveAdapterRegistry.getAdapter(parameterType); |
| 253 | + if (reactiveAdapter == null || reactiveAdapter.isNoValue()) { |
| 254 | + continue; |
| 255 | + } |
| 256 | + Class<?>[] groups = determineValidationGroups(method.getParameters()[i]); |
| 257 | + if (groups == null) { |
| 258 | + continue; |
| 259 | + } |
| 260 | + SpringValidatorAdapter validatorAdapter = validatorAdapterSupplier.get(); |
| 261 | + MethodParameter param = new MethodParameter(method, i); |
| 262 | + arguments[i] = (reactiveAdapter.isMultiValue() ? |
| 263 | + Flux.from(reactiveAdapter.toPublisher(arguments[i])).doOnNext(value -> |
| 264 | + validate(validatorAdapter, adaptViolations, target, method, param, value, groups)) : |
| 265 | + Mono.from(reactiveAdapter.toPublisher(arguments[i])).doOnNext(value -> |
| 266 | + validate(validatorAdapter, adaptViolations, target, method, param, value, groups))); |
| 267 | + } |
| 268 | + return arguments; |
| 269 | + } |
| 270 | + |
| 271 | + @Nullable |
| 272 | + private static Class<?>[] determineValidationGroups(Parameter parameter) { |
| 273 | + Validated validated = AnnotationUtils.findAnnotation(parameter, Validated.class); |
| 274 | + if (validated != null) { |
| 275 | + return validated.value(); |
| 276 | + } |
| 277 | + Valid valid = AnnotationUtils.findAnnotation(parameter, Valid.class); |
| 278 | + if (valid != null) { |
| 279 | + return new Class<?>[0]; |
| 280 | + } |
| 281 | + return null; |
| 282 | + } |
| 283 | + |
| 284 | + @SuppressWarnings("unchecked") |
| 285 | + private static <T> void validate( |
| 286 | + SpringValidatorAdapter validatorAdapter, boolean adaptViolations, |
| 287 | + Object target, Method method, MethodParameter parameter, Object argument, Class<?>[] groups) { |
| 288 | + |
| 289 | + if (adaptViolations) { |
| 290 | + Errors errors = new BeanPropertyBindingResult(argument, argument.getClass().getSimpleName()); |
| 291 | + validatorAdapter.validate(argument, errors); |
| 292 | + if (errors.hasErrors()) { |
| 293 | + ParameterErrors paramErrors = new ParameterErrors(parameter, argument, errors, null, null, null); |
| 294 | + List<ParameterValidationResult> results = Collections.singletonList(paramErrors); |
| 295 | + throw new MethodValidationException(MethodValidationResult.create(target, method, results)); |
| 296 | + } |
| 297 | + } |
| 298 | + else { |
| 299 | + Set<ConstraintViolation<T>> violations = validatorAdapter.validate((T) argument, groups); |
| 300 | + if (!violations.isEmpty()) { |
| 301 | + throw new ConstraintViolationException(violations); |
| 302 | + } |
| 303 | + } |
| 304 | + } |
| 305 | + } |
| 306 | + |
209 | 307 | }
|
0 commit comments