I have a generic super class which I'd like to validate:
abstract class Generic<T> {
// ... other validated fields
private T value;
}
Then I have several concrete classes and for each of it I'd like to appy specific validations on a value
field.
E.g. for the below class I'd like to ensure that the value
field is not less than 0.
class ConcreteInt<Integer> {
// @Min(0)
}
Another example, for the below class I'd like to ensure that the value
is not blank.
class ConcreteString<String> {
// @NotBlank
}
Where should I put my validation annotations on a concrete classes to make them work?
CodePudding user response:
I would suggest to use the Template design pattern and apply the validations accordingly.
First we have the generic class that accepts any type as T
with an abstract method that the children must implement.
public abstract class Generic<T> {
protected T value;
abstract T getValue();
}
then we have a child for integer implementation with annotations @NotNull
and @PositiveOrZero
.
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
public class ConcreteInteger extends Generic<Integer> {
public ConcreteInteger(Integer v) {
value = v;
}
@Override
@NotNull
@PositiveOrZero
public Integer getValue() {
return value;
}
}
and a child for string implementation with annotations @NotNull
, and @NotBlank
. Here we already see that we can have different validations.
package com.yieldlab.reporting.dto.analytics;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class ConcreteString extends Generic<String> {
public ConcreteString(String v) {
value = v;
}
@Override
@NotNull
@NotBlank
public String getValue() {
return value;
}
}
But it won't validate if we just use the annotations. We have to invoke the javax.validation.Validator
API. Here is one example:
import javax.validation.Configuration;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class BeanFieldValidationExample {
private static final Validator validator;
static {
Configuration<?> config = Validation.byDefaultProvider().configure();
ValidatorFactory factory = config.buildValidatorFactory();
validator = factory.getValidator();
factory.close();
}
public static void main(String[] args) {
// it won't validate by just call the constructor
ConcreteInteger concreteInteger0 = new ConcreteInteger(0);
System.out.println(concreteInteger0.value);
ConcreteInteger concreteInteger1 = new ConcreteInteger(1);
System.out.println(concreteInteger1.value);
ConcreteInteger concreteIntegerMinus1 = new ConcreteInteger(-1);
System.out.println(concreteIntegerMinus1.value);
ConcreteInteger concreteIntegerNull = new ConcreteInteger(null);
System.out.println(concreteIntegerNull.value);
ConcreteString concreteString0 = new ConcreteString("some string");
System.out.println(concreteString0.value);
ConcreteString concreteString1 = new ConcreteString("");
System.out.println(concreteString1.value);
ConcreteString concreteStringNull = new ConcreteString(null);
System.out.println(concreteStringNull.value);
// invoking the validator API will in validate our beans validator.validate(concreteInteger0).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
validator.validate(concreteInteger1).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
validator.validate(concreteIntegerMinus1).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
validator.validate(concreteIntegerNull).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
validator.validate(concreteString0).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
validator.validate(concreteString1).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
validator.validate(concreteStringNull).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
}
private static void printErrorConcreteInteger(ConstraintViolation<ConcreteInteger> concreteIntegerConstraintViolation) {
System.out.println(concreteIntegerConstraintViolation.getPropertyPath() " " concreteIntegerConstraintViolation.getMessage());
}
private static void printErrorConcreteString(ConstraintViolation<ConcreteString> concreteStringConstraintViolation) {
System.out.println(concreteStringConstraintViolation.getPropertyPath() " " concreteStringConstraintViolation.getMessage());
}
}
then we can see the output:
0
1
-1
null
some string
null
value must be greater than or equal to 0
value must not be null
value must not be blank
value must not be null
value must not be blank
CodePudding user response:
I have found a solution. It's enough to override the getter of the class and place the annotations there:
class ConcreteString<String> {
@Override
@NotBlank
public String getValue(){
return super.getValue();
}
}