Not able to validate data from enum using hibernate validator - java

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
}
}

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 do I use EL to evaluate the length of an array in a (JSR 380) constraint message?

I have a constraint on which I want to use EL to tune the message to the circumstances. Dependent on the length of an array, I want to show a different message. However, I'm having trouble getting the length of that array.
What am I doing wrong?
import org.junit.jupiter.api.Test;
import javax.validation.*;
import java.lang.annotation.*;
import static org.assertj.core.api.Assertions.assertThat;
public class FooTest {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
#Test
public void foo() {
var violations = validator.validate(new ObjectWithFoo());
assertThat(violations).extracting("message")
.containsExactly("Field value should be foo");
}
#Test
public void foos() {
var violations = validator.validate(new ObjectWithFoos());
assertThat(violations).extracting("message")
.containsExactly("Field value should be one of [foo, bar, baz]");
}
#Foo(foos = {"foo"})
private static class ObjectWithFoo{}
#Foo(foos = {"foo", "bar", "baz"})
private static class ObjectWithFoos{}
#Constraint(validatedBy = FooValidator.class)
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Foo{
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] foos();
// This is the message I want to tune to the length of the array.
// If the array contains just one element, I want to show a different message.
// Note that 'one of foos' is a placeholder; I still need to figure out
// how to display the array in that case.
String message() default "Field value should be ${foos.length == 1 ? foos[0] : 'one of foos'}";
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
Foo[] value();
}
}
public static class FooValidator implements ConstraintValidator<Foo, Object> {
#Override
public void initialize(Foo constraintAnnotation) {
}
#Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
return false; // for this test, we want the validation to fail
}
}
}
Unfortunately, this throws an exception:
20:03:52.810 [main] WARN org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver -
HV000148: An exception occurred during evaluation of EL expression '${foos.length == 1 ? foos[0] : 'one of $foos'}'
java.lang.NumberFormatException: For input string: "length"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Integer.parseInt(Integer.java:652)

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

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 :)

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;
}
}

Categories

Resources