Java Hibernate Validator requires one or another

This is an extension of my previous question . I followed Dennis R's answer and am using hibernate-validator. Is there a way to require one or more fields to be specified in a json request, but not both? From my previous post in the Request class, I want the user to pass in either an identifier or a code, but NOT both.

I found this resource that might be the right solution for me, but I don’t quite understand what is happening there, why it works and, frankly, it looks too detailed. Is this the only way to do this?

+4
source share
5 answers

As I said earlier and following from, you can achieve what you want with the help of the following code:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
public @interface FieldMatch {

    String message() default "something is wrong!";

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

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

    /**
     * @return The first field
     */
    String first();

    /**
     * @return The second field
     */
    String second();

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        FieldMatch[] value();
    }

    public static class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {

        private String firstFieldName;
        private String secondFieldName;

        @Override
        public void initialize(FieldMatch fieldMatch) {
            firstFieldName = fieldMatch.first();
            secondFieldName = fieldMatch.second();
        }

        public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
            try {
                final Object firstObj = getProperty(object, firstFieldName);
                final Object secondObj = getProperty(object, secondFieldName);

                if(firstObj == null && secondObj == null || firstObj != null && secondObj != null) {
                    return false;
                }
            } catch (final Exception ignore) {
                // ignore
            }
            return true;
        }

        private Object getProperty(Object value, String fieldName) {
            Field[] fields = value.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.getName().equals(fieldName)) {
                    field.setAccessible(true);
                    try {
                        return field.get(value);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            return null;
        }
    }

}

Using:

@FieldMatch.List({
        @FieldMatch(first = "name", second = "people"),
        @FieldMatch(first = "age", second = "abc")
})
public class Foo {

    private String name;
    private List<String> people;
    private int age;
    private Boolean abc; 
}

The only difference for you is that you do not want to check if this content is equal only if one field is null and the other is not.

EDIT:

To get an object on your ExceptionHandler, as asked using comments, you simply wrap the exception around the user-defined one and pass the object when you throw it, i.e.

public class CustomException extends Exception {

    private String message;
    private Object model;

    public CustomException(String message, Object model) {
        super(message);
        this.model = model;
    }

    public Object getModel() {
        return model;
    }
}

With this, you can simply do it like this:

@ExceptionHandler(CustomException.class)
public ModelAndView handleCustomException(CustomException ex) {
    Object obj = ex.getModel();
    //do whatever you have to
}
+5
source

Alternatively, you can use the Group . For example, this Request.java:

public class Request {

public interface IdOrCodeValidationGroup {}

    @NotNull
    @NotEmpty
    private String id;

    @Digits(integer=4, fraction=0)
    private double code;

    @NotNull
    @NotEmpty
    private String name;

    @AssertTrue(groups = IdOrCodeValidationGroup.class)
    private boolean idOrCodeFilled;

    public Request(String id, double code, String name) {
        this.id = id;
        this.code = code;
        this.name = name;
    }

    public boolean isIdOrCodeFilled() {
        if (id == null && code > 0) {
            idOrCodeFilled = true;
        } else if (id != null && code == 0) {
            idOrCodeFilled = true;
        } else idOrCodeFilled = false;
        return idOrCodeFilled;
    }
}

And then use the validator as follows:

@Test
public void testValidation() {
    // Of course all of this valid. No group at all.
    final Request request = new Request("ID-001", 111, "Data 1");
    final Set<ConstraintViolation<Request>> fails = this.validator.validate(request);
    Assert.assertTrue(fails.isEmpty());
}

@Test
public void testValidationWithGroup() {
    // We use "IdOrCodeValidationGroup.class" group, thus this is invalid.
    Request request = new Request("ID-001", 111, "Data 1");
    Set<ConstraintViolation<Request>> fails = this.validator.validate(request, IdOrCodeValidationGroup.class);
    Assert.assertFalse(fails.isEmpty());

    // Lets make one of constraint true; In this case, we set code = 0.
    request = new Request("ID-002", 0, "Data 2");
    fails = this.validator.validate(request, IdOrCodeValidationGroup.class);
    // Passed!
    Assert.assertFalse(fails.isEmpty()); 
}

. ( "so-36365734" ). Bean .

.

+1

. . :

@org.hibernate.annotations.Check

, , , ...

@Entity
@org.hibernate.annotations.Check(constraints = "(field1 IS NULL OR field2 IS NULL) AND (field1 IS NOT NULL OR field2 IS NOT NULL)")
public class MyEntity{
    String field1;
    Double field2;
}

, . Hibernate ( , hibernate).

, / , .

Postgres : ALTER TABLE my_entity ADD CONSTRAINT my_entity_check CHECK (( 1 IS NULL field2 IS NULL) AND (1 2 ));

Postgres

Oracle Check Constraints

SQL, , hibernate , SQL. , hibernate , , ..

0

: JSR-303 (, Integer, Double ..), (, int, Double ..) all - . . RequestCheck, :

@RequestCheck
public final class Request {
    //... Use Double, *not* double.
    private Double code;
    // ...
}

RequestCheck :

/**
 * Request entity validator.
 */
@Target({ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = RequestValidator.class)
@Documented
public @interface ImpCheck {
    Class<?>[] groups() default {};
    String message() default "";
    Class<? extends Payload>[] payload() default {};
}

JSR-303:

/**
 * Validator for the Request entity.
 */
public class RequestValidator implements ConstraintValidator<RequestCheck, Request> {
    @Override
    public void initialize(final RequestCheck arg0) {
        // Required due to implementing ConstraintValidator but can be left blank.
    }

    @Override
    public boolean isValid(final Request request, final ConstraintValidatorContext ctx) {
        // Default validity is true until proven otherwise.
        boolean valid = true;

        // Disable default ConstraintViolation so a customised message can be set instead.
        ctx.disableDefaultConstraintViolation();

        // Either id or code (but not both) must be specified.
        // Note: ^ is Java XOR operator, i.e. one or the other must be true but not both.
        if (!(request.getId() == null ^ request.getCode() == null)) {
            valid = false;
            ctx.buildConstraintViolationWithTemplate(
                "Request - either id or code (but not both) must be specified.")
                .addConstraintViolation();
        }

        return valid;
    }
}
0

JSON, , JSON .

In this answer, you can find a hint about how the circuit might look: json schema, how do I need one field or the other or one of the other two, but not all of them?

In this tutorial, you can find an approach to how to apply JSON schema validation. check json against schema in java

0
source

All Articles