001/* 002 * Copyright 2002-2018 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; 021import javax.validation.ConstraintViolation; 022import javax.validation.ConstraintViolationException; 023import javax.validation.Validation; 024import javax.validation.Validator; 025import javax.validation.ValidatorFactory; 026 027import org.aopalliance.intercept.MethodInterceptor; 028import org.aopalliance.intercept.MethodInvocation; 029import org.hibernate.validator.HibernateValidator; 030 031import org.springframework.beans.factory.FactoryBean; 032import org.springframework.beans.factory.SmartFactoryBean; 033import org.springframework.core.BridgeMethodResolver; 034import org.springframework.core.annotation.AnnotationUtils; 035import org.springframework.util.ClassUtils; 036import org.springframework.util.ReflectionUtils; 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 4.0, this functionality requires either a Bean Validation 1.1 provider 054 * (such as Hibernate Validator 5.x) or the Bean Validation 1.0 API with Hibernate Validator 055 * 4.3. The actual provider will be autodetected and automatically adapted. 056 * 057 * @author Juergen Hoeller 058 * @since 3.1 059 * @see MethodValidationPostProcessor 060 * @see javax.validation.executable.ExecutableValidator 061 * @see org.hibernate.validator.method.MethodValidator 062 */ 063public class MethodValidationInterceptor implements MethodInterceptor { 064 065 private static Method forExecutablesMethod; 066 067 private static Method validateParametersMethod; 068 069 private static Method validateReturnValueMethod; 070 071 static { 072 try { 073 forExecutablesMethod = Validator.class.getMethod("forExecutables"); 074 Class<?> executableValidatorClass = forExecutablesMethod.getReturnType(); 075 validateParametersMethod = executableValidatorClass.getMethod( 076 "validateParameters", Object.class, Method.class, Object[].class, Class[].class); 077 validateReturnValueMethod = executableValidatorClass.getMethod( 078 "validateReturnValue", Object.class, Method.class, Object.class, Class[].class); 079 } 080 catch (Exception ex) { 081 // Bean Validation 1.1 ExecutableValidator API not available 082 } 083 } 084 085 086 private volatile Validator validator; 087 088 089 /** 090 * Create a new MethodValidationInterceptor using a default JSR-303 validator underneath. 091 */ 092 public MethodValidationInterceptor() { 093 this(forExecutablesMethod != null ? Validation.buildDefaultValidatorFactory() : 094 HibernateValidatorDelegate.buildValidatorFactory()); 095 } 096 097 /** 098 * Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory. 099 * @param validatorFactory the JSR-303 ValidatorFactory to use 100 */ 101 public MethodValidationInterceptor(ValidatorFactory validatorFactory) { 102 this(validatorFactory.getValidator()); 103 } 104 105 /** 106 * Create a new MethodValidationInterceptor using the given JSR-303 Validator. 107 * @param validator the JSR-303 Validator to use 108 */ 109 public MethodValidationInterceptor(Validator validator) { 110 this.validator = validator; 111 } 112 113 114 @Override 115 @SuppressWarnings("unchecked") 116 public Object invoke(MethodInvocation invocation) throws Throwable { 117 // Avoid Validator invocation on FactoryBean.getObjectType/isSingleton 118 if (isFactoryBeanMetadataMethod(invocation.getMethod())) { 119 return invocation.proceed(); 120 } 121 122 Class<?>[] groups = determineValidationGroups(invocation); 123 124 if (forExecutablesMethod != null) { 125 // Standard Bean Validation 1.1 API 126 Object execVal; 127 try { 128 execVal = ReflectionUtils.invokeMethod(forExecutablesMethod, this.validator); 129 } 130 catch (AbstractMethodError err) { 131 // Probably an adapter (maybe a lazy-init proxy) without BV 1.1 support 132 Validator nativeValidator = this.validator.unwrap(Validator.class); 133 execVal = ReflectionUtils.invokeMethod(forExecutablesMethod, nativeValidator); 134 // If successful, store native Validator for further use 135 this.validator = nativeValidator; 136 } 137 138 Method methodToValidate = invocation.getMethod(); 139 Set<ConstraintViolation<?>> result; 140 141 try { 142 result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod, 143 execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups); 144 } 145 catch (IllegalArgumentException ex) { 146 // Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011 147 // Let's try to find the bridged method on the implementation class... 148 methodToValidate = BridgeMethodResolver.findBridgedMethod( 149 ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass())); 150 result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateParametersMethod, 151 execVal, invocation.getThis(), methodToValidate, invocation.getArguments(), groups); 152 } 153 if (!result.isEmpty()) { 154 throw new ConstraintViolationException(result); 155 } 156 157 Object returnValue = invocation.proceed(); 158 result = (Set<ConstraintViolation<?>>) ReflectionUtils.invokeMethod(validateReturnValueMethod, 159 execVal, invocation.getThis(), methodToValidate, returnValue, groups); 160 if (!result.isEmpty()) { 161 throw new ConstraintViolationException(result); 162 } 163 return returnValue; 164 } 165 166 else { 167 // Hibernate Validator 4.3's native API 168 return HibernateValidatorDelegate.invokeWithinValidation(invocation, this.validator, groups); 169 } 170 } 171 172 private boolean isFactoryBeanMetadataMethod(Method method) { 173 Class<?> clazz = method.getDeclaringClass(); 174 175 // Call from interface-based proxy handle, allowing for an efficient check? 176 if (clazz.isInterface()) { 177 return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) && 178 !method.getName().equals("getObject")); 179 } 180 181 // Call from CGLIB proxy handle, potentially implementing a FactoryBean method? 182 Class<?> factoryBeanType = null; 183 if (SmartFactoryBean.class.isAssignableFrom(clazz)) { 184 factoryBeanType = SmartFactoryBean.class; 185 } 186 else if (FactoryBean.class.isAssignableFrom(clazz)) { 187 factoryBeanType = FactoryBean.class; 188 } 189 return (factoryBeanType != null && !method.getName().equals("getObject") && 190 ClassUtils.hasMethod(factoryBeanType, method.getName(), method.getParameterTypes())); 191 } 192 193 /** 194 * Determine the validation groups to validate against for the given method invocation. 195 * <p>Default are the validation groups as specified in the {@link Validated} annotation 196 * on the containing target class of the method. 197 * @param invocation the current MethodInvocation 198 * @return the applicable validation groups as a Class array 199 */ 200 protected Class<?>[] determineValidationGroups(MethodInvocation invocation) { 201 Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class); 202 if (validatedAnn == null) { 203 validatedAnn = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class); 204 } 205 return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]); 206 } 207 208 209 /** 210 * Inner class to avoid a hard-coded Hibernate Validator 4.3 dependency. 211 */ 212 private static class HibernateValidatorDelegate { 213 214 public static ValidatorFactory buildValidatorFactory() { 215 return Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory(); 216 } 217 218 @SuppressWarnings("deprecation") 219 public static Object invokeWithinValidation(MethodInvocation invocation, Validator validator, Class<?>[] groups) 220 throws Throwable { 221 222 org.hibernate.validator.method.MethodValidator methodValidator = 223 validator.unwrap(org.hibernate.validator.method.MethodValidator.class); 224 Set<org.hibernate.validator.method.MethodConstraintViolation<Object>> result = 225 methodValidator.validateAllParameters( 226 invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups); 227 if (!result.isEmpty()) { 228 throw new org.hibernate.validator.method.MethodConstraintViolationException(result); 229 } 230 Object returnValue = invocation.proceed(); 231 result = methodValidator.validateReturnValue( 232 invocation.getThis(), invocation.getMethod(), returnValue, groups); 233 if (!result.isEmpty()) { 234 throw new org.hibernate.validator.method.MethodConstraintViolationException(result); 235 } 236 return returnValue; 237 } 238 } 239 240}