/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.core.internal.validation;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Provides;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matcher;
import com.google.inject.matcher.Matchers;
import org.seedstack.shed.reflect.Annotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.Constraint;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@ValidationConcern
class ValidationModule extends AbstractModule {
private static final Logger LOGGER = LoggerFactory.getLogger(ValidationModule.class);
private final ValidatorFactory validatorFactory;
ValidationModule(ValidatorFactory validatorFactory) {
this.validatorFactory = validatorFactory;
}
@Override
protected void configure() {
bind(ValidatorFactory.class).toInstance(validatorFactory);
enableValidationOnInjectionPoints();
if (isDynamicValidationSupported()) {
configureDynamicValidation();
}
}
private void enableValidationOnInjectionPoints() {
bindListener(staticValidationMatcher(), new StaticValidationProvisionListener(validatorFactory));
}
private void configureDynamicValidation() {
bindInterceptor(Matchers.any(), dynamicValidationMatcher(), new MethodValidationInterceptor(validatorFactory));
}
private boolean isDynamicValidationSupported() {
ExecutableValidator executableValidator = null;
try {
executableValidator = validatorFactory.getValidator().forExecutables();
} catch (Throwable t) {
LOGGER.warn("Unable to create the dynamic validator, support for dynamic validation disabled", t);
}
return executableValidator != null;
}
@Provides
Validator provideValidator() {
return validatorFactory.getValidator();
}
private Matcher<? super Binding<?>> staticValidationMatcher() {
return new AbstractMatcher<Binding<?>>() {
@Override
public boolean matches(Binding<?> binding) {
Class<?> candidate = binding.getKey().getTypeLiteral().getRawType();
for (Field field : candidate.getDeclaredFields()) {
for (Annotation annotation : field.getAnnotations()) {
if (hasConstraintOrValidAnnotation(annotation)) {
return true;
}
}
}
return false;
}
};
}
private Matcher<Method> dynamicValidationMatcher() {
return new AbstractMatcher<Method>() {
@Override
public boolean matches(Method method) {
return shouldValidateParameters(method) || shouldValidateReturnType(method);
}
};
}
private boolean shouldValidateParameters(Method candidate) {
for (Annotation[] annotationsForOneParameter : candidate.getParameterAnnotations()) {
for (Annotation annotation : annotationsForOneParameter) {
if (hasConstraintOrValidAnnotation(annotation)) {
return true;
}
}
}
return false;
}
private boolean shouldValidateReturnType(Method candidate) {
for (Annotation annotation : candidate.getAnnotations()) {
if (hasConstraintOrValidAnnotation(annotation)) {
return true;
}
}
return false;
}
private boolean hasConstraintOrValidAnnotation(Annotation annotation) {
return Annotations.on(annotation.annotationType()).includingMetaAnnotations().find(Constraint.class).isPresent() ||
Valid.class.equals(annotation.annotationType());
}
}