cahen
cahen

Reputation: 16636

Dropwizard support for validation in non-Resource classes (JSR-303)

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?

Upvotes: 0

Views: 450

Answers (1)

Paul Samsotha
Paul Samsotha

Reputation: 208984

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.

Upvotes: 1

Related Questions