With dropwizard validation, can I access the DB to

2019-04-16 00:12发布

My problem is the following:

I am using dropwizard for a project and I have used the validation framework happily and successfully so far. My validation works fine and it is used in the standard way. This is what I have:

Request class:

import javax.validation.constraints.NotNull;

import MandatoryFieldLengthCheck;

public class InitiatePaymentRequest implements PaymentRequest {

    @NotNull(message = "Mandatory input field missing")
    @MandatoryFieldLengthCheck(value = 32)
    protected String   transactionId;
}

Then the annotation class:

@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = MandatoryFieldLengthCheckValidator.class)
@Documented
public @interface MandatoryFieldLengthCheck {

    String message() default "Invalid input data";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    long value();

}

Then the validator class:

public class MandatoryFieldLengthCheckValidator implements ConstraintValidator<MandatoryFieldLengthCheck, Object> {

    private static final Logger LOGGER = LoggerFactory.getLogger(MandatoryFieldLengthCheckValidator.class);

    private long                length;

    @Override
    public void initialize(final MandatoryFieldLengthCheck constraintAnnotation) {
        this.length = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(final Object object, final ConstraintValidatorContext constraintContext) {
        LOGGER.debug("isValid [{}]", object);

        // fields with a MandatoryFieldLengthCheck will also have a NotNull annotation so
        // nulls will be handled there, to avoid returning an invalid field value return true here
        if (object == null) {
            return true;
        }

        if (object instanceof Long || object instanceof Integer || object instanceof Short) {
            return object.toString().length() <= this.length;
        } else if (object instanceof String) {
            return (((String) object).replace(" ", "").length() > 0) && (((String) object).length() <= this.length);
        } else {
            return false;
        }
    }
}

That is working and it is fine.

Now, from MandatoryFieldLengthCheckValidator, I want to insert a record in the database (it has to be the database), like validation fail for auditing purposes.

I have tried to inject a DAO without success, not sure if it possible or not. The validator is created on the fly, so I cannot control what I pass or not unless there is some magic that inject the dao.

So my question is how can I do that and more specifically:

  • Can I inject the DAO?
  • Does it make sense to access your DB from your validation code?
  • Can I reconfigure the validation engine to get this functionality?
  • Is there any other framework that can let me do it?
  • Any other way to do this? AOP? using Static methods?

Even if you do not exactly, if you point me in the right direction, it will appreciate it. Thanks.

1条回答
beautiful°
2楼-- · 2019-04-16 00:28

soz, I'm late to the party. There is a way to get this working as well (injecting into validators). Mind you, the comment that validators should be simple still holds true, however sometimes you just need to inject some other object for correct validation. So this is how you can achieve this:

So first off, I assume you have DI set up. If not, look into https://github.com/xvik/dropwizard-guicey - it integrates guice into dropwizard and does a rather great job too. Once you have this set up, you're almost there.

Validation works on the Validator class which will create your validators on the fly - you are correct in that. This creation will NOT be able to inject any values as it's just not meant to do this out of the box.

You can create a Module (handled by guice) that will create the validator for you. This can look like that:

public class ValidationModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(GuiceConstraintValidationFactory.class).in(Singleton.class);
    }

    @Provides
    public Validator validator(GuiceConstraintValidationFactory validatoFactory) {
        return Validation.byDefaultProvider().configure().constraintValidatorFactory(validatoFactory).buildValidatorFactory().getValidator();
    }

}

Note that this creates a GuiceConstraintValidationFactory. This factory is simply a delegate which will return the correct validator. It is a delegate so that old validators still work (NotNull and friends). Here's some code:

public class GuiceConstraintValidationFactory implements ConstraintValidatorFactory {

    private ConstraintValidatorFactory delegate;
    private Set<ConstraintValidator> validators;

    @Inject
    public GuiceConstraintValidationFactory(final Set<ConstraintValidator> validators) {
        this.validators = validators;
        delegate = new ConstraintValidatorFactoryImpl();
    }

    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
        for(ConstraintValidator<?, ?> validator : validators) {
            if(validator.getClass() == key) {
                return (T) validator;
            }
        }
        return delegate.getInstance(key);
    }

    @Override
    public void releaseInstance(ConstraintValidator<?, ?> instance) {
        delegate.releaseInstance(instance);
    }

}

Note how this class gets injected validators. These validators are guice handles, hence they can have injected fields. For example:

@Plugin(ConstraintValidator.class)
public class ResourceValidator implements ConstraintValidator<ValidResource, String>{


    private static final Logger log = Logger.getLogger(ResourceValidator.class);

    @Inject
    private MyConfiguration conf;

    @Override
    public void initialize(ValidResource constraintAnnotation) {
        // nothing to do here
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(value == null) {
            return true;
        }
        String fullPath = conf.getBasePath() + File.separator + value;
        return new File(fullPath).exists();
    }

}

This validator is guice handled. The @Plugin annotation enabled guicey dropwizard to create the instance, inject it and then group all of them in a set (makes it easier to inject them) See the GuiceConstraintValidationFactory where a Set of validators is injected into the factory. This factory will now be used by the default Validator. When requesting a new validator, the factory checks if a guice managed validator exists for the required type. If not, it will default back to the normal validator implementation. If yes, it will return the guice managed validator. This enables you to add new validators on the fly and have guice create, manage and inject them.

I hope that helps.

-- Artur

查看更多
登录 后发表回答