私は、JSR-303 _javax.validation
_を使用して少しカスタム検証を行うことを探しています。
フィールドがあります。そして、特定の値がこのフィールドに入力された場合、他のいくつかのフィールドがnull
ではないことを要求したいです。
私はこれを理解しようとしています。説明を見つけるのを助けるために私がこれを何と呼ぶか正確にはわかりません。
任意の助けをいただければ幸いです。私はこれにかなり新しいです。
現時点では、カスタム制約を考えています。しかし、注釈内から依存フィールドの値をテストする方法がわかりません。基本的に、注釈からパネルオブジェクトにアクセスする方法がわかりません。
_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;
}
}
}
_
panel.status.getValue();
が私にトラブルを与えています。これをどのように達成するかわからない。
この場合、クラスレベルで(オブジェクトのフィールドにアクセスできるようにするために)別のフィールドに特定の値がある場合にのみ1つのフィールドが必要であることを検証するカスタムバリデーターを作成することをお勧めします。 2つのフィールド名を取得し、これらの2つのフィールドのみを操作する汎用バリデーターを記述する必要があることに注意してください。複数のフィールドを要求するには、各フィールドにこのバリデーターを追加する必要があります。
次のコードをアイデアとして使用します(テストしていません)。
検証インターフェイス
/**
* 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();
}
}
バリデーターの実装
/**
* 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;
}
}
バリデーターの使用例
@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
}
バリデーターの実装では、commons-beanutils
ライブラリのBeanUtils
クラスを使用しますが、 Spring FrameworkのBeanWrapperImpl
も使用できます。
この素晴らしい回答も参照してください: Hibernate Validator(JSR 303)を使用したクロスフィールド検証
Trueに検証する必要があるメソッドを定義し、@AssertTrue
上部の注釈:
@AssertTrue
private boolean isOk() {
return someField != something || otherField != null;
}
メソッドは「is」で始まる必要があります。
カスタム 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
}
このトピックに関する関連質問 も参照してください。
これは私の考えです。できるだけシンプルにしようとしました。
インターフェース:
@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();
}
検証の実装:
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();
}
}
使用法:
@OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {
private StateType stateType;
private ModeType modeType;
}
メッセージ:
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}
別のアプローチは、すべての依存フィールドを含むオブジェクトを返す(保護された)ゲッターを作成することです。例:
public class MyBean {
protected String status;
protected String name;
@StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}
StatusAndSomethingValidatorは、StatusAndSomething.statusおよびStatusAndSomething.somethingにアクセスして、依存チェックを行えるようになりました。