001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.validation.beanvalidation; 018 019import java.lang.reflect.Method; 020import java.util.Set; 021 022import javax.validation.ConstraintViolation; 023import javax.validation.ConstraintViolationException; 024import javax.validation.Validation; 025import javax.validation.Validator; 026import javax.validation.ValidatorFactory; 027import javax.validation.executable.ExecutableValidator; 028 029import org.aopalliance.intercept.MethodInterceptor; 030import org.aopalliance.intercept.MethodInvocation; 031 032import org.springframework.beans.factory.FactoryBean; 033import org.springframework.beans.factory.SmartFactoryBean; 034import org.springframework.core.BridgeMethodResolver; 035import org.springframework.core.annotation.AnnotationUtils; 036import org.springframework.util.ClassUtils; 037import org.springframework.validation.annotation.Validated; 038 039/** 040 * An AOP Alliance {@link MethodInterceptor} implementation that delegates to a 041 * JSR-303 provider for performing method-level validation on annotated methods. 042 * 043 * <p>Applicable methods have JSR-303 constraint annotations on their parameters 044 * and/or on their return value (in the latter case specified at the method level, 045 * typically as inline annotation). 046 * 047 * <p>E.g.: {@code public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)} 048 * 049 * <p>Validation groups can be specified through Spring's {@link Validated} annotation 050 * at the type level of the containing target class, applying to all public service methods 051 * of that class. By default, JSR-303 will validate against its default group only. 052 * 053 * <p>As of Spring 5.0, this functionality requires a Bean Validation 1.1+ provider. 054 * 055 * @author Juergen Hoeller 056 * @since 3.1 057 * @see MethodValidationPostProcessor 058 * @see javax.validation.executable.ExecutableValidator 059 */ 060public class MethodValidationInterceptor implements MethodInterceptor { 061 062 private final Validator validator; 063 064 065 /** 066 * Create a new MethodValidationInterceptor using a default JSR-303 validator underneath. 067 */ 068 public MethodValidationInterceptor() { 069 this(Validation.buildDefaultValidatorFactory()); 070 } 071 072 /** 073 * Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory. 074 * @param validatorFactory the JSR-303 ValidatorFactory to use 075 */ 076 public MethodValidationInterceptor(ValidatorFactory validatorFactory) { 077 this(validatorFactory.getValidator()); 078 } 079 080 /** 081 * Create a new MethodValidationInterceptor using the given JSR-303 Validator. 082 * @param validator the JSR-303 Validator to use 083 */ 084 public MethodValidationInterceptor(Validator validator) { 085 this.validator = validator; 086 } 087 088 089 @Override 090 public Object invoke(MethodInvocation invocation) throws Throwable { 091 // Avoid Validator invocation on FactoryBean.getObjectType/isSingleton 092 if (isFactoryBeanMetadataMethod(invocation.getMethod())) { 093 return invocation.proceed(); 094 } 095 096 Class<?>[] groups = determineValidationGroups(invocation); 097 098 // Standard Bean Validation 1.1 API 099 ExecutableValidator execVal = this.validator.forExecutables(); 100 Method methodToValidate = invocation.getMethod(); 101 Set<ConstraintViolation<Object>> result; 102 103 try { 104 result = execVal.validateParameters( 105 invocation.getThis(), methodToValidate, invocation.getArguments(), groups); 106 } 107 catch (IllegalArgumentException ex) { 108 // Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011 109 // Let's try to find the bridged method on the implementation class... 110 methodToValidate = BridgeMethodResolver.findBridgedMethod( 111 ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass())); 112 result = execVal.validateParameters( 113 invocation.getThis(), methodToValidate, invocation.getArguments(), groups); 114 } 115 if (!result.isEmpty()) { 116 throw new ConstraintViolationException(result); 117 } 118 119 Object returnValue = invocation.proceed(); 120 121 result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups); 122 if (!result.isEmpty()) { 123 throw new ConstraintViolationException(result); 124 } 125 126 return returnValue; 127 } 128 129 private boolean isFactoryBeanMetadataMethod(Method method) { 130 Class<?> clazz = method.getDeclaringClass(); 131 132 // Call from interface-based proxy handle, allowing for an efficient check? 133 if (clazz.isInterface()) { 134 return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) && 135 !method.getName().equals("getObject")); 136 } 137 138 // Call from CGLIB proxy handle, potentially implementing a FactoryBean method? 139 Class<?> factoryBeanType = null; 140 if (SmartFactoryBean.class.isAssignableFrom(clazz)) { 141 factoryBeanType = SmartFactoryBean.class; 142 } 143 else if (FactoryBean.class.isAssignableFrom(clazz)) { 144 factoryBeanType = FactoryBean.class; 145 } 146 return (factoryBeanType != null && !method.getName().equals("getObject") && 147 ClassUtils.hasMethod(factoryBeanType, method)); 148 } 149 150 /** 151 * Determine the validation groups to validate against for the given method invocation. 152 * <p>Default are the validation groups as specified in the {@link Validated} annotation 153 * on the containing target class of the method. 154 * @param invocation the current MethodInvocation 155 * @return the applicable validation groups as a Class array 156 */ 157 protected Class<?>[] determineValidationGroups(MethodInvocation invocation) { 158 Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class); 159 if (validatedAnn == null) { 160 validatedAnn = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class); 161 } 162 return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]); 163 } 164 165}