wake-up-neo.com

JSR 303-Validierung: Wenn ein Feld "etwas" ist, sollten diese anderen Felder nicht null sein

Ich freue mich auf eine kleine benutzerdefinierte Validierung mit JSR-303 javax.validation.

Ich habe ein Feld. Und wenn ein bestimmter Wert in dieses Feld eingegeben wird, möchte ich verlangen, dass einige andere Felder nicht null sind.

Ich versuche das herauszufinden. Ich weiß nicht genau, wie ich das nennen würde, um eine Erklärung zu finden.

Jede Hilfe wäre dankbar. Das ist mir ziemlich neu.

Im Moment denke ich an eine benutzerdefinierte Einschränkung. Ich bin mir jedoch nicht sicher, wie ich den Wert des abhängigen Felds in der Annotation testen soll. Grundsätzlich bin ich mir nicht sicher, wie ich über die Annotation auf das Panel-Objekt zugreifen soll.

public class StatusValidator implements ConstraintValidator<NotNull, String> {

    @Override
    public void initialize(NotNull constraintAnnotation) {}

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if ("Canceled".equals(panel.status.getValue())) {
            if (value != null) {
                return true;
            }
        } else {
            return false;
        }
    }
}

Es ist die panel.status.getValue();, die mir Probleme bereitet. Ich bin mir nicht sicher, wie ich das erreichen soll.

70
Eric

In diesem Fall empfehle ich, einen benutzerdefinierten Validator zu schreiben, der auf Klassenebene überprüft (damit wir auf die Felder des Objekts zugreifen können), dass ein Feld nur erforderlich ist, wenn ein anderes Feld einen bestimmten Wert hat. Beachten Sie, dass Sie einen generischen Validator schreiben sollten, der 2 Feldnamen erhält und nur mit diesen 2 Feldern arbeitet. Um mehr als ein Feld zu benötigen, sollten Sie diesen Validator für jedes Feld hinzufügen.

Verwenden Sie den folgenden Code als Idee (ich habe ihn nicht getestet).

  • Validator-Schnittstelle

    /**
     * Validates that field {@code dependFieldName} is not null if
     * field {@code fieldName} has value {@code fieldValue}.
     **/
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
    @Documented
    public @interface NotNullIfAnotherFieldHasValue {
    
        String fieldName();
        String fieldValue();
        String dependFieldName();
    
        String message() default "{NotNullIfAnotherFieldHasValue.message}";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    
        @Target({TYPE, ANNOTATION_TYPE})
        @Retention(RUNTIME)
        @Documented
        @interface List {
            NotNullIfAnotherFieldHasValue[] value();
        }
    
    }
    
  • Validator-Implementierung

    /**
     * Implementation of {@link NotNullIfAnotherFieldHasValue} validator.
     **/
    public class NotNullIfAnotherFieldHasValueValidator
        implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> {
    
        private String fieldName;
        private String expectedFieldValue;
        private String dependFieldName;
    
        @Override
        public void initialize(NotNullIfAnotherFieldHasValue annotation) {
            fieldName          = annotation.fieldName();
            expectedFieldValue = annotation.fieldValue();
            dependFieldName    = annotation.dependFieldName();
        }
    
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext ctx) {
    
            if (value == null) {
                return true;
            }
    
            try {
                String fieldValue       = BeanUtils.getProperty(value, fieldName);
                String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
    
                if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null) {
                    ctx.disableDefaultConstraintViolation();
                    ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())
                        .addNode(dependFieldName)
                        .addConstraintViolation();
                        return false;
                }
    
            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
                throw new RuntimeException(ex);
            }
    
            return true;
        }
    
    }
    
  • Validator-Verwendungsbeispiel

    @NotNullIfAnotherFieldHasValue.List({
        @NotNullIfAnotherFieldHasValue(
            fieldName = "status",
            fieldValue = "Canceled",
            dependFieldName = "fieldOne"),
        @NotNullIfAnotherFieldHasValue(
            fieldName = "status",
            fieldValue = "Canceled",
            dependFieldName = "fieldTwo")
    })
    public class SampleBean {
        private String status;
        private String fieldOne;
        private String fieldTwo;
    
        // getters and setters omitted
    }
    

Beachten Sie, dass die Validator-Implementierung die Klasse BeanUtils aus der Bibliothek commons-beanutils Verwendet. Sie können jedoch auch BeanWrapperImpl aus Spring Framework verwenden.

Siehe auch diese großartige Antwort: Feldübergreifende Validierung mit dem Hibernate Validator (JSR 303)

87
Slava Semushin

Definieren Sie eine Methode, die auf true validiert werden muss, und setzen Sie das @AssertTrue Anmerkung oben drauf:

  @AssertTrue
  private boolean isOk() {
    return someField != something || otherField != null;
  }

Die Methode muss mit 'is' beginnen.

84
h22

Sie sollten custom DefaultGroupSequenceProvider<T> :

ConditionalValidation.Java

// Marker interface
public interface ConditionalValidation {}

MyCustomFormSequenceProvider.Java

public class MyCustomFormSequenceProvider
    implements DefaultGroupSequenceProvider<MyCustomForm> {

    @Override
    public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm) {

        List<Class<?>> sequence = new ArrayList<>();

        // Apply all validation rules from ConditionalValidation group
        // only if someField has given value
        if ("some value".equals(myCustomForm.getSomeField())) {
            sequence.add(ConditionalValidation.class);
        }

        // Apply all validation rules from default group
        sequence.add(MyCustomForm.class);

        return sequence;
    }
}

MyCustomForm.Java

@GroupSequenceProvider(MyCustomFormSequenceProvider.class)
public class MyCustomForm {

    private String someField;

    @NotEmpty(groups = ConditionalValidation.class)
    private String fieldTwo;

    @NotEmpty(groups = ConditionalValidation.class)
    private String fieldThree;

    @NotEmpty
    private String fieldAlwaysValidated;


    // getters, setters omitted
}

Siehe auch verwandte Frage zu diesem Thema .

13
user11153

Hier ist meine Sichtweise, versucht es so einfach wie möglich zu halten.

Die Schnittstelle:

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = OneOfValidator.class)
@Documented
public @interface OneOf {

    String message() default "{one.of.message}";

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

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

    String[] value();
}

Validierungsimplementierung:

public class OneOfValidator implements ConstraintValidator<OneOf, Object> {

    private String[] fields;

    @Override
    public void initialize(OneOf annotation) {
        this.fields = annotation.value();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {

        BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);

        int matches = countNumberOfMatches(wrapper);

        if (matches > 1) {
            setValidationErrorMessage(context, "one.of.too.many.matches.message");
            return false;
        } else if (matches == 0) {
            setValidationErrorMessage(context, "one.of.no.matches.message");
            return false;
        }

        return true;
    }

    private int countNumberOfMatches(BeanWrapper wrapper) {
        int matches = 0;
        for (String field : fields) {
            Object value = wrapper.getPropertyValue(field);
            boolean isPresent = detectOptionalValue(value);

            if (value != null && isPresent) {
                matches++;
            }
        }
        return matches;
    }

    private boolean detectOptionalValue(Object value) {
        if (value instanceof Optional) {
            return ((Optional) value).isPresent();
        }
        return true;
    }

    private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
        context.disableDefaultConstraintViolation();
        context
            .buildConstraintViolationWithTemplate("{" + template + "}")
            .addConstraintViolation();
    }

}

Verwendung:

@OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {

    private StateType stateType;

    private ModeType modeType;

}

Mitteilungen:

one.of.too.many.matches.message=Only one of the following fields can be specified: {value}
one.of.no.matches.message=Exactly one of the following fields must be specified: {value}
6
hochas

Ein anderer Ansatz wäre, einen (geschützten) Getter zu erstellen, der ein Objekt zurückgibt, das alle abhängigen Felder enthält. Beispiel:

public class MyBean {
  protected String status;
  protected String name;

  @StatusAndSomethingValidator
  protected StatusAndSomething getStatusAndName() {
    return new StatusAndSomething(status,name);
  }
}

StatusAndSomethingValidator kann jetzt auf StatusAndSomething.status und StatusAndSomething.something zugreifen und eine abhängige Prüfung durchführen.

3
Michael Wyraz