package de.zalando.sprocwrapper.proxy.executors; import java.lang.annotation.ElementType; import java.util.Set; import javax.sql.DataSource; import javax.validation.ConstraintValidator; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import javax.validation.constraints.NotNull; import javax.validation.groups.Default; import javax.validation.metadata.ConstraintDescriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.reflect.Invokable; import com.google.common.reflect.Parameter; import de.zalando.sprocwrapper.proxy.InvocationContext; import de.zalando.sprocwrapper.validation.MethodConstraintValidationHolder; import de.zalando.sprocwrapper.validation.NotNullValidator; import de.zalando.sprocwrapper.validation.SimpleConstraintDescriptor; import de.zalando.sprocwrapper.validation.SimplePath; /** * This Executor wraps stored procedure calls that use advisory locks and / or need different statement timeouts set. * * @author carsten.wolters */ public class ValidationExecutorWrapper implements Executor { private static final ConstraintValidator<NotNull, Object> NOT_NULL_VALIDATOR = new NotNullValidator(); private final Executor executor; private static final Logger LOG = LoggerFactory.getLogger(ValidationExecutorWrapper.class); private static ValidatorFactory factory; static { try { factory = Validation.buildDefaultValidatorFactory(); } catch (final Exception e) { LOG.error("Could not build default validator factory", e); } } public ValidationExecutorWrapper(final Executor e) { executor = e; } @SuppressWarnings("rawtypes") @Override public Object executeSProc(final DataSource ds, final String sql, final Object[] args, final int[] types, final InvocationContext invocationContext, final Class returnType) { if (factory != null) { final Validator validator = factory.getValidator(); final Set<ConstraintViolation<?>> constraintViolations = Sets.newHashSet(); if (args != null) { validateParameters(invocationContext, validator, invocationContext.getArgs(), constraintViolations); } if (!constraintViolations.isEmpty()) { throw new ConstraintViolationException("SPROC call does not meet all constraints. Aborting.", constraintViolations); } final Object result = executor.executeSProc(ds, sql, args, types, invocationContext, returnType); if (result != null) { constraintViolations.addAll(validator.validate(result)); if (!constraintViolations.isEmpty()) { throw new ConstraintViolationException("SPROC return object does not meet all constraints.", constraintViolations); } } return result; } // else do nothing return executor.executeSProc(ds, sql, args, types, invocationContext, returnType); } private void validateParameters(final InvocationContext invocationContext, final Validator validator, final Object[] originalArgs, final Set<ConstraintViolation<?>> constraintViolations) { if (originalArgs != null) { Invokable<?, Object> invokable = Invokable.from(invocationContext.getMethod()); for (int i = 0; i < originalArgs.length; i++) { Object arg = originalArgs[i]; if (!NOT_NULL_VALIDATOR.isValid(arg, null)) { // JSR 303 doesn't support method level validation // we should provide at least a dummy implementation to detect @NotNull annotations // we should migrate our implementation to bean validation 1.1 when possible final Parameter parameter = invokable.getParameters().get(i); final NotNull annotation = parameter.getAnnotation(NotNull.class); if (annotation != null) { final String parameterName = "arg" + i; final ConstraintDescriptor<NotNull> descriptor = new SimpleConstraintDescriptor<NotNull>( annotation, ImmutableSet.<Class<?>>of(Default.class), ImmutableList.<Class<? extends ConstraintValidator<NotNull, ?>>>of( NotNullValidator.class), null); final ConstraintViolation<Object> violation = new MethodConstraintValidationHolder<Object>( // message "may not be null", // messageTemplate annotation.message(), // rootBean invocationContext.getProxy(), // leafBean (the object the method is executed on) invocationContext.getProxy(), // propertyPath SimplePath.createPathForMethodParameter(invocationContext.getMethod(), parameterName), // invalidValue null, // constraintDescriptor descriptor, // elementType ElementType.PARAMETER, // method invocationContext.getMethod(), // parameterIndex i, // parameterName parameterName); constraintViolations.add(violation); } } else { constraintViolations.addAll(validator.validate(arg)); } } } } }