Je cherche à faire une petite validation personnalisée avec JSR-303 javax.validation
.
J'ai un champ Et si une certaine valeur est entrée dans ce champ, je veux exiger que quelques autres champs ne soient pas null
.
J'essaie de comprendre cela. Je ne sais pas exactement comment j'appellerais ceci pour aider à trouver une explication.
Toute aide serait appréciée. Je suis assez nouveau pour cela.
Pour le moment, je pense à une contrainte personnalisée. Mais je ne suis pas sûr de savoir comment tester la valeur du champ dépendant à partir de l'annotation. Fondamentalement, je ne sais pas comment accéder à l'objet de panneau à partir de l'annotation.
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;
}
}
}
C'est la panel.status.getValue();
qui me pose des problèmes .. je ne sais pas comment y parvenir.
Dans ce cas, je suggère d'écrire un validateur personnalisé, qui validera au niveau de la classe (pour nous permettre d'accéder aux champs de l'objet), qu'un champ n'est requis que si un autre champ a une valeur particulière. Notez que vous devez écrire un validateur générique qui obtient 2 noms de champs et ne travailler qu'avec ces 2 champs. Pour exiger plusieurs champs, vous devez ajouter ce validateur pour chaque champ.
Utilisez le code suivant comme une idée (je ne l’ai pas testée).
Interface de validation
/**
* 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();
}
}
Mise en œuvre du validateur
/**
* 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;
}
}
Exemple d'utilisation du validateur
@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
}
Notez que l'implémentation du validateur utilise BeanUtils
class from commons-beanutils
bibliothèque mais vous pouvez aussi utiliser BeanWrapperImpl
de Spring Framework .
Voir aussi cette excellente réponse: Validation de champs croisés avec Hibernate Validator (JSR 303)
Définit la méthode qui doit valider à true et place le @AssertTrue
annotation sur le dessus:
@AssertTrue
private boolean isOk() {
return someField != something || otherField != null;
}
La méthode doit commencer par "est".
Vous devriez utiliser custom DefaultGroupSequenceProvider<T>
:
// Marker interface
public interface ConditionalValidation {}
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;
}
}
@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
}
Voir aussi question connexe sur ce sujet .
Voici mon point de vue, essayé de le garder aussi simple que possible.
L'interface:
@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();
}
Validation mise en oeuvre:
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();
}
}
Usage:
@OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {
private StateType stateType;
private ModeType modeType;
}
Messages:
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}
Une approche différente consisterait à créer un getter (protégé) qui renvoie un objet contenant tous les champs dépendants. Exemple:
public class MyBean {
protected String status;
protected String name;
@StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}
StatusAndSomethingValidator peut maintenant accéder à StatusAndSomething.status et StatusAndSomething.something et effectuer une vérification dépendante.