0

According to this link:

If you need to validate entities outside of resource endpoints, the validator can be accessed in the Environment when the application is first ran.

Which means that the @Valid below won't work and I have to programatically use the validator on the profile object and do something with the errors that come back:

public class ProfilesManager {  
    ...
    public void createProfile(@Valid Profile profile) {
        Set<ConstraintViolation<Profile>> errors = validator.validate(profile);
        ...
    }
}

In Spring Boot, all I have to do is annotate it with @Validated and a ConstraintViolationException will automatically be thrown:

@Validated
@Component
public class ProfilesManager { 
    public void createProfile(@Valid Profile profile) {
        // if invalid, exception thrown before getting here
    }
}

Is there an equivalent solution for Dropwizard, official or 3rd party?

cahen
  • 15,807
  • 13
  • 47
  • 78

1 Answers1

1

It is easy to implement this on your own using HK2's interception service. All you need to do is provide a MethodInteceptor that inspects the method parameters for the @Valid annotation and validate those parameters with the Validator

public class ValidationMethodInterceptor implements MethodInterceptor {

    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        for (int i = 0; i < args.length; i++) {
            Parameter parameter = invocation.getMethod().getParameters()[i];
            if (parameter.getAnnotation(Valid.class) != null) {
                handleValidation(args[i]);
            }
        }
        return invocation.proceed();
    }

    private void handleValidation(Object arg) {
        Set<ConstraintViolation<Object>> constraintViolations
                = validator.validate(arg);

        if (!constraintViolations.isEmpty()) {
            throw new IllegalArgumentException(
                    "constraint violations in bean argument");
        }
    }
}

In your InterceptionService implementation, you can decide which services should be validated with a custom Filter that looks for a custom @Validated annotation on the service class

public class ValidatedFilter implements Filter {

    @Override
    public boolean matches(Descriptor descriptor) {
        try {
            return Class.forName(descriptor.getImplementation())
                    .isAnnotationPresent(Validated.class);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

public class ValidationInterceptionService implements InterceptionService {

    private final static MethodInterceptor METHOD_INTERCEPTOR = new ValidationMethodInterceptor();
    private final static List<MethodInterceptor> METHOD_LIST = Collections.singletonList(METHOD_INTERCEPTOR);

    @Override
    public Filter getDescriptorFilter() {
        return new ValidatedFilter();
    }

    @Override
    public List<MethodInterceptor> getMethodInterceptors(Method method) {
        for (Parameter parameter: method.getParameters()) {
            if (parameter.isAnnotationPresent(Valid.class)) {
                return METHOD_LIST;
            }
        }
        return null;
    }

    @Override
    public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> c) {
        return null;
    }
}

See complete example with test case in this GitHub repository.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • This is an interesting solution and is similar to how Spring does it. However, keep in mind my goal is to enable all the features in the Bean Validation framework, including `@Min`, `@Max`, custom annotations, etc. In method and class levels. Or in other words, I'm looking for a Dropwizard version of `org.springframework.validation.beanvalidation.MethodValidationInterceptor`, but it doesn't seem to exist. – cahen May 14 '18 at 10:13
  • You could try to just ignore the check for `@Valid` and validate everything. – Paul Samsotha May 14 '18 at 14:18
  • If you have an attribute like `@Min(0) int age` and you call `.validate(age)`, it won't work. Which means I'd have to create wrappers for everything, so I can put the annotations in attributes rather than parameters. – cahen May 14 '18 at 15:03
  • 1
    You know you can integrate Spring with Dropwizard. You can just use Spring at the service level. – Paul Samsotha May 14 '18 at 18:12