blob: 55ff4810e11431c12d34c97b798f1e98c626da2c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.bval.guice;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.bval.jsr303.extensions.MethodValidator;
/**
* Method interceptor for {@link Validate} annotation.
*
* @version $Id$
*/
public final class ValidateMethodInterceptor implements MethodInterceptor {
private static final Class<?>[] CAUSE_TYPES = new Class[]{ Throwable.class };
private static final Class<?>[] MESSAGE_CAUSE_TYPES = new Class[]{ String.class, Throwable.class };
/**
* The {@link ValidatorFactory} reference.
*/
@Inject
private ValidatorFactory validatorFactory;
/**
* Sets the {@link ValidatorFactory} reference.
*
* @param validatorFactory the {@link ValidatorFactory} reference
*/
public void setValidatorFactory(ValidatorFactory validatorFactory) {
this.validatorFactory = validatorFactory;
}
/**
* {@inheritDoc}
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
Validate validate = invocation.getMethod().getAnnotation(Validate.class);
Validator validator = this.validatorFactory.getValidator();
MethodValidator methodValidator = validator.unwrap(MethodValidator.class);
Set<ConstraintViolation<?>> constraintViolations = new HashSet<ConstraintViolation<?>>();
Class<?> clazz = invocation.getMethod().getDeclaringClass();
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
Class<?>[] groups = validate.groups();
constraintViolations.addAll(methodValidator.validateParameters(clazz,
method,
arguments,
groups));
if (!constraintViolations.isEmpty()) {
throw getException(new ConstraintViolationException(
String.format("Validation error when calling method '%s' with arguments %s",
method,
Arrays.deepToString(arguments)),
constraintViolations),
validate.rethrowExceptionsAs(),
validate.exceptionMessage(),
arguments);
}
Object returnedValue = invocation.proceed();
if (validate.validateReturnedValue()) {
constraintViolations.addAll(methodValidator.validateReturnedValue(clazz, method, returnedValue, groups));
if (!constraintViolations.isEmpty()) {
throw getException(new ConstraintViolationException(
String.format("Method '%s' returned a not valid value %s",
method,
returnedValue),
constraintViolations),
validate.rethrowExceptionsAs(),
validate.exceptionMessage(),
arguments);
}
}
return returnedValue;
}
/**
* Define the {@link Throwable} has to be thrown when a validation error
* occurs and the user defined the custom error wrapper.
*
* @param exception the occurred validation error.
* @param exceptionWrapperClass the user defined custom error wrapper.
* @return the {@link Throwable} has o be thrown.
*/
private static Throwable getException(ConstraintViolationException exception,
Class<? extends Throwable> exceptionWrapperClass,
String exceptionMessage,
Object[] arguments) {
// check the thrown exception is of same re-throw type
if (exceptionWrapperClass == ConstraintViolationException.class) {
return exception;
}
// re-throw the exception as new exception
Throwable rethrowEx = null;
String errorMessage;
Object[] initargs;
Class<?>[] initargsType;
if (exceptionMessage.length() != 0) {
errorMessage = String.format(exceptionMessage, arguments);
initargs = new Object[]{ errorMessage, exception };
initargsType = MESSAGE_CAUSE_TYPES;
} else {
initargs = new Object[]{ exception };
initargsType = CAUSE_TYPES;
}
Constructor<? extends Throwable> exceptionConstructor = getMatchingConstructor(exceptionWrapperClass, initargsType);
if (exceptionConstructor != null) {
try {
rethrowEx = exceptionConstructor.newInstance(initargs);
} catch (Exception e) {
errorMessage = String.format("Impossible to re-throw '%s', it needs the constructor with %s argument(s).",
exceptionWrapperClass.getName(),
Arrays.toString(initargsType));
rethrowEx = new RuntimeException(errorMessage, e);
}
} else {
errorMessage = String.format("Impossible to re-throw '%s', it needs the constructor with %s or %s argument(s).",
exceptionWrapperClass.getName(),
Arrays.toString(CAUSE_TYPES),
Arrays.toString(MESSAGE_CAUSE_TYPES));
rethrowEx = new RuntimeException(errorMessage);
}
return rethrowEx;
}
@SuppressWarnings("unchecked")
private static <E extends Throwable> Constructor<E> getMatchingConstructor(Class<E> type,
Class<?>[] argumentsType) {
Class<? super E> currentType = type;
while (Object.class != currentType) {
for (Constructor<?> constructor : currentType.getConstructors()) {
if (Arrays.equals(argumentsType, constructor.getParameterTypes())) {
return (Constructor<E>) constructor;
}
}
currentType = currentType.getSuperclass();
}
return null;
}
}