How to apply a custom annotation to a List<String>? - java

I created an annotation in order to validate the value of a String field (like an enum) :
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = GenericStringSetValidator.class)
public #interface ValidStringValue {
String message() default "Must be a valid value. Found: ${validatedValue}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] values() default {};
}
And the GenericStringSetValidator used by the annotation :
public class GenericStringSetValidator implements ConstraintValidator<ValidStringValue, String> {
private Set<String> validValues;
#Override
public void initialize(ValidStringValue constraint) {
validValues = Arrays.stream(constraint.values())
.collect(toSet());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null) return false;
return validValues.contains(value);
}
}
I already use this in the following class whithout any trouble :
public class SampleClass{
private String id;
#ValidStringValue(values = {"IN_PROGRESS", "COMPLETED", "DELETED"})
private String status;
}
But I also want to use it in an other class with List<String> in order to validate each items of the List. Is it possible to configure my ValidStringValue interface to make it usable on a String and also a List<String> ?
public class SampleClassWithList{
private String id;
#ValidStringValue(values = {"A", "B", "C"})
private List<String> sampleList;
}
Thanks a lot for your help :)

Related

How can i test a DTO validation of 2 fields #interface

I am a java beginner and I need help with testing this:
what I am trying to do: I need at least one of the fields with value to accept it.
How should test my implementation?
My classes:
#Constraint(validatedBy = BankOrBarPresenceValidador.class)
#Target({ TYPE, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Documented
public #interface VerifyBankOrBarPresence {
String message() default "{constraints.VerifyBankOrBarPresence.message}";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
String barCodeInvoice();
String bankDestinationNumber();
}
Implementation:
public class BankOrBarPresenceValidador implements ConstraintValidator<VerifyBankOrBarPresence, Object> {
private String barCodeInvoice;
private String bankDestinationNumber;
#Override
public void initialize(VerifyBankOrBarPresence constraintAnnotation) {
barCodeInvoice = constraintAnnotation.barCodeInvoice();
bankDestinationNumber = constraintAnnotation.bankDestinationNumber();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
try{
boolean barcodeIsPresent = barCodeInvoice.length() > 0;
boolean bankDestinationNumberIsPresent = bankDestinationNumber.length() > 0;
return barcodeIsPresent || bankDestinationNumberIsPresent;
}
catch (final Exception e) {
throw new BusinessException("Failed validating if bar code or bank destination number are present", e);
}
}
}
MY DTO:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString
#VerifyBankOrBarPresence(barCodeInvoice = "barCodeInvoice", bankDestinationNumber="bankDestinationNumber", message = "Bar Code Invoice or bank Destination Number code not be null")
public class VoucherDTO implements Serializable {
private String barCodeInvoice;
private String bankDestinationNumber;
}

How to add multiple constraint violations for the same property node?

I have a custom ConstraintValidator. What i want to do, is to add multiple constraint violations for the same property node, with the same message, and different dynamic payload. Is this possible?
Custom annotation:
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = CustomFieldConstraintValidator.class)
public #interface CustomFieldConstraint {
String message() default "message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom constraint validator:
public class CustomFieldConstraintValidator implements ConstraintValidator<CustomFieldConstraint, Integer> {
#Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
var valid = true;
var hibernateConstraintValidatorContext = context.unwrap(HibernateConstraintValidatorContext.class);
hibernateConstraintValidatorContext.disableDefaultConstraintViolation();
if (value > 0) {
valid = false;
hibernateConstraintValidatorContext.withDynamicPayload(Map.of("must_be_less_than", 0))
.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("field")
.addConstraintViolation();
}
if (value > 5) {
valid = false;
hibernateConstraintValidatorContext.withDynamicPayload(Map.of("must_be_less_than", 5))
.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("field")
.addConstraintViolation();
}
return valid;
}
}
Validator test:
public class HibernateValidatorTest {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
#Test
void multipleFailuresForSameField() {
var foo = new Foo(10);
var constraintViolations = validator.validate(foo);
assertEquals(2, constraintViolations.size());
}
public static class Foo {
#CustomFieldConstraint
private int field;
public Foo(int field) {
this.field = field;
}
}
}
The test is failing with expected: <2> but was: <1>.
You have this issue because ConstraintViolation are returned as a set and, by design, the dynamic payload is not included in the equals() method. See https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/java/org/hibernate/validator/internal/engine/ConstraintViolationImpl.java#L251 .
Thus your 2 constraints are considered equal at the Set level.
You need to change the way you do things and enrich the payload with both information instead.

#NotNull Annotation does not work on Double

I am trying to validate Double in the request of my dropwizard application. Below is the code for same
import javax.validation.constraints.NotNull;
#PATH("/path")
public class MyResource {
#GET
public Response addNumber( #QueryParam("first") #NotNull Double first, #QueryParam("second") #NotNull Double second) {
return Response.ok("added").build();
}
}
I get the below error when querying the resource localhost/path?first=46.76&second=67.56
{
"errors": [
"query param first may not be null",
"query param second may not be null"
]
}
If we remove the #NotNull from the resource the resource responds successfully.
Why does #NotNull say it should be not null although the values are present?
I had the same requirement. So implemented custom validator.
#Retention(RUNTIME)
#Target({ElementType.FIELD})
#Constraint(validatedBy = NotZeroDoubleValidatorImpl.class)
public #interface NotZeroDoubleValidator {
String message() default "{Double value is required}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Field to validate against 0.
*/
double field() default 0;
}
And
public class NotZeroDoubleValidatorImpl implements ConstraintValidator<NotZeroDoubleValidator, Double> {
#Override
public void initialize(final NotZeroDoubleValidator notZeroDoubleValidator) {
}
#Override
public boolean isValid(final Double value, final ConstraintValidatorContext context) {
return Double.compare(value, 0) != 0;
}
}

Not able to validate data from enum using hibernate validator

I am using hibernate validator for validating DTO object. Here facing one chalange not able check data is available or not in Enum
I have used below enum
public enum DeliveryMethodEnum {
IMMEDIATE(1),
SCHEDULED(2);
private Integer deliveryMethod;
private DeliveryMethodEnum(Integer deliveryMethod) {
this.deliveryMethod = deliveryMethod;
}
public Integer getDeliveryMethod() {
return deliveryMethod;
}
}
In DTO use custom annotation as below
#EnumValidator(message = "1008", enumClass =
DeliveryMethodEnum.class) private Integer deliveryMethod;
Request is below
{
"deliveryMethod": 8
}
It should show invalid request but its not valided from enum.
I have below custome annotation code
#Documented
#Constraint(validatedBy = EnumValidatorImpl.class)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#NotNull(message = "Value cannot be null")
#ReportAsSingleViolation
public #interface EnumValidator {
Class<? extends Enum<?>> enumClass();
String message() default "Value is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class EnumValidatorImpl implements ConstraintValidator<EnumValidator, Integer> {
List<String> valueList = null;
#Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return !valueList.contains(String.valueOf(value));
}
#Override
public void initialize(EnumValidator constraintAnnotation) {
valueList = new ArrayList<>();
Class<? extends Enum<?>> enumClass = constraintAnnotation.enumClass();
#SuppressWarnings("rawtypes")
Enum[] enumValArr = enumClass.getEnumConstants();
for (#SuppressWarnings("rawtypes")
Enum enumVal : enumValArr) {
valueList.add(enumVal.toString().toUpperCase());
}
}
}
This is because there is error in your EnumValidatorImpl class.
Following line
!valueList.contains(String.valueOf(value));
Actual value of valueList: [IMMEDIATE, SCHEDULED]
and value: 8 from body payload {"deliveryMethod": 8}
So validation will fail always.
Also !valueList.contains this will be valid for non-enum value,
if you need to validate for only enum values then remove ! symbol.
Solutions
1. Accept as String from request as
{
"deliveryMethod": "IMMEDIATE"
}
or
{
"deliveryMethod": "SCHEDULED"
}
Hence need to updateDTO, Now value is one of the enum/not and gets validated
Create valueList as [1, 2] and validate.
Here is updated EnumValidatorImpl.java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class EnumValidatorImpl implements ConstraintValidator<EnumValidator, Integer> {
List<Integer> valueList = null;
#Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return valueList.contains(value);
}
#Override
public void initialize(EnumValidator constraintAnnotation) {
valueList = new ArrayList<>();
Class<? extends Enum<?>> enumClass = constraintAnnotation.enumClass();
Enum[] enumValArr = enumClass.getEnumConstants();
valueList = Arrays.stream(enumValArr)
.map(anEnum -> ((DeliveryMethodEnum) anEnum).deliveryMethod)
.collect(Collectors.toList()); // This will make problem, since it will only be used for DeliveryMethodEnum enum type and not for any other enum, if it is not the problem then this can be done,else I could not think of any other way
}
}

JSR-303 Bean Validation - Custom Constraint Multiple Annotations to One Validator

When writing custom constraints is it possible to have multiple annotations validated by one validator implementation. For example I have several annotations which stipulate different #size annotations but I want them all to point at the same validator class which does some global checking i.e. all must be match a certain regex. As far as I can see the implementation takes in one Annotation Type.
One annotation
#Target( { METHOD, FIELD, ANNOTATION_TYPE, TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {UCNValidator.class})
#Documented
#Size(min = 9, max = 9, message = "{exactlength}")
public #interface UCN {
String message() default "{invalidFormat}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String fieldName() default "ucn";
}
The validator
public class UCNValidator implements ConstraintValidator<UCN, String>
{
private String pattern = "[a-zA-Z].*";
private String fieldName;
#Override
public void initialize( UCN constraintAnnotation )
{
this.fieldName = constraintAnnotation.fieldName();
}
#Override
public boolean isValid( String value, ConstraintValidatorContext constraintValidatorContext )
{
if ( value != null )
{
if ( !value.matches(pattern) )
{
//do some stuff
return false;
}
}
return true;
}
There doesn't seem to be a way to access other values from an object when validating one of its properties. The solution I use is to put the annotation on the class, then the validator will get the entire object in to validate against, and you can access just the info you need to perform the validation.
Here's one I wrote to compare two different properties of an object against each other:
#Target(TYPE)
#Retention(RUNTIME)
#Constraint(validatedBy = LessThanValidator.class)
#Documented
public #interface LessThan {
String message() default "{com.bullethq.constraints.LessThan}";
String bigValueProperty();
String littleValueProperty();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then the validator class is:
public class LessThanValidator implements ConstraintValidator<LessThan, Object> {
private LessThan constraint;
public void initialize(LessThan constraintAnnotation) {
constraint = constraintAnnotation;
}
public boolean isValid(Object object, ConstraintValidatorContext cvc) {
Object bigValue = getValue(object, constraint.bigValueProperty());
Object littleValue = getValue(object, constraint.littleValueProperty());
// If one of the values is null, then we do not perform validation.
if (bigValue == null || littleValue == null) {
return true;
}
if (bigValue instanceof Comparable && littleValue instanceof Comparable) {
boolean valid = ((Comparable<Object>) bigValue).compareTo(littleValue) > 0;
if (!valid) {
// If the values are not valid, then build a custom violations which has the correct path in it.
cvc.buildConstraintViolationWithTemplate(cvc.getDefaultConstraintMessageTemplate())
.addNode(constraint.littleValueProperty())
.addConstraintViolation().disableDefaultConstraintViolation();
}
return valid;
}
throw new IllegalArgumentException("Properties " + constraint.bigValueProperty() + " and " + constraint.littleValueProperty() + " both need to be comparable in " + object.getClass());
}
}
The getValue() method is just a static method using reflection to get the value from the object.

Categories

Resources