/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.validation.beanvalidation; import java.lang.reflect.Method; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import javax.validation.executable.ExecutableValidator; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ClassUtils; import org.springframework.validation.annotation.Validated; /** * An AOP Alliance {@link MethodInterceptor} implementation that delegates to a * JSR-303 provider for performing method-level validation on annotated methods. * * <p>Applicable methods have JSR-303 constraint annotations on their parameters * and/or on their return value (in the latter case specified at the method level, * typically as inline annotation). * * <p>E.g.: {@code public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)} * * <p>Validation groups can be specified through Spring's {@link Validated} annotation * at the type level of the containing target class, applying to all public service methods * of that class. By default, JSR-303 will validate against its default group only. * * <p>As of Spring 5.0, this functionality requires a Bean Validation 1.1 provider. * * @author Juergen Hoeller * @since 3.1 * @see MethodValidationPostProcessor * @see javax.validation.executable.ExecutableValidator */ public class MethodValidationInterceptor implements MethodInterceptor { private final Validator validator; /** * Create a new MethodValidationInterceptor using a default JSR-303 validator underneath. */ public MethodValidationInterceptor() { this(Validation.buildDefaultValidatorFactory()); } /** * Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory. * @param validatorFactory the JSR-303 ValidatorFactory to use */ public MethodValidationInterceptor(ValidatorFactory validatorFactory) { this(validatorFactory.getValidator()); } /** * Create a new MethodValidationInterceptor using the given JSR-303 Validator. * @param validator the JSR-303 Validator to use */ public MethodValidationInterceptor(Validator validator) { this.validator = validator; } @Override @SuppressWarnings("unchecked") public Object invoke(MethodInvocation invocation) throws Throwable { Class<?>[] groups = determineValidationGroups(invocation); // Standard Bean Validation 1.1 API ExecutableValidator execVal = this.validator.forExecutables(); Method methodToValidate = invocation.getMethod(); Set<ConstraintViolation<Object>> result; try { result = execVal.validateParameters( invocation.getThis(), methodToValidate, invocation.getArguments(), groups); } catch (IllegalArgumentException ex) { // Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011 // Let's try to find the bridged method on the implementation class... methodToValidate = BridgeMethodResolver.findBridgedMethod( ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass())); result = execVal.validateParameters( invocation.getThis(), methodToValidate, invocation.getArguments(), groups); } if (!result.isEmpty()) { throw new ConstraintViolationException(result); } Object returnValue = invocation.proceed(); result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups); if (!result.isEmpty()) { throw new ConstraintViolationException(result); } return returnValue; } /** * Determine the validation groups to validate against for the given method invocation. * <p>Default are the validation groups as specified in the {@link Validated} annotation * on the containing target class of the method. * @param invocation the current MethodInvocation * @return the applicable validation groups as a Class array */ protected Class<?>[] determineValidationGroups(MethodInvocation invocation) { Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class); if (validatedAnn == null) { validatedAnn = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class); } return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]); } }