Martin Mucha
Martin Mucha

Reputation: 3091

Separate validation annotation and it's validator in separate modules

Situation: you have module with DTO objects used in your API, so that other project(s) can reuse then when sending requests. These DTO classes does have bean-validation annotations in them. And you would like to use your custom validations to validate DTO "arriving" via requests. The sender typically does not validate outgoing data, IIUC, and might not be interested in importing validators along with annotations.

Problem(?): bean-validation is defined in a way, where annotation defines who implements it (which is incorrect and it should be otherwise around imo), with possibility to specify empty array as annotation validator (seems like hack) and then pairing is done via manual hashmap manipulations instead of stuff like service loader etc.

How do you do this?

Upvotes: 4

Views: 980

Answers (2)

Martin Mucha
Martin Mucha

Reputation: 3091

I we need to separate interface class and validator implementation into separate modules, it's possible. And even in a way, which I said in original question, that should be used. In API module you declare validation for example as:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
@ReportAsSingleViolation
public @interface AnyUuid {
    //...

notice validatedBy = {}. The validator implementation looks like:

public class AnyUuidValidator implements ConstraintValidator<AnyUuid, Object> {
//...

and pairing can be setup using service loader(see javadoc if you don't know how that works). Put into file META-INF/services/javax.validation.ConstraintValidator FQDN of AnyUuidValidator shown above. And all other custom validators. And that should be it.

There is a bug I found with this. If I'm not mistaken. If you have DTO you cannot change (~annotate with constraints) and still want to validate them via bean validation, you can register validation definitions via xml file. If you are doing so and use service loader for pairing definition and implementation of custom validators, there is probably some bug and your custom validators won't be found. So verify this scenario before relying on service loader. But maybe I'm wrong, for me it was feasible to drop this validation trivially so I did to save some time and could ignore this.

Upvotes: 0

CWils
CWils

Reputation: 11

I agree that the annotation defining the validator does feel backwards. While not ideal, I've been able to work around this by separating my custom ConstraintValidator into an interface and implementation.

Example:

In api module define constraint and interface validator

@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint
{
}
public interface MyConstraintValidator
    extends ConstraintValidator<MyConstraint, String>
{
}

In your service module define the implementation

public class MyConstraintValidatorImpl implements MyConstraintValidator
{
  private FooService foo;

  @Override
  public boolean isValid( String value, ConstraintValidatorContext ctx)
  {
    // Implement your constraint logic
  }
}

Upvotes: 1

Related Questions