Bean Validationのエラーメッセージには、以下のように制約アノテーションに指定した属性値を埋め込むことができます。(なお、本エントリーで扱う内容は、Hibernate Validatorを使う前提になります)
@Size(min = 4, max = 16)
private String foo;
// ...
Bean bean = new Bean();
bean.foo = "123";
Set<ConstraintViolation<Bean>> violations = vb.validate(bean);
// ...
上記のケースで生成されるエラーメッセージは、「size must be between 4 and 16」になります。
アノテーション属性値以外を埋め込みたいと思った背景は・・・
今携わっている案件で、入力チェック時に参照する基準値を外部設定(実際にはプロパティファイル)から取得する必要性に迫られました。Bean Validationを使わずロジックでチェックするという選択肢もありましたが、入力チェックはできるだけBean Validationの仕組みを使って行いたかったので・・・Bean Validationで実現する方法を調査しました。
具体的にはどんな制約アノテーションを作ったの?
実際に作成したものではありませんが、「指定日数以降の日付であること」をチェックするアノテーションを作成し、「指定日数」の部分をプロパティファイルから取得するような制約アノテーションを作成することにしました。
@DaysLater(days = "foo.daysLater", defaultValue = 5)
private LocalDate fooDate;
foo.daysLater=10
com.example.validation.DaysLater.message = must be {days} days later
チェックはできたがメッセージが残念・・・
制約違反した際のエラーメッセージは「must be 10 days later」としたいところですが・・・残念ながらデフォルトの動作では「must be foo.daysLater days later」となってしまいます。ま〜当然の結果なのですが・・・
Bean Validation仕様準拠で対応可能か?
ちゃんと調べていませんが・・・ざっと仕様書を見た感じだとBean Validationの仕様内で対応する方法はなさそうでした。ただ・・・メッセージを組み立てる際にEL式が使えるので・・・EL式を使いこなせば対応できるかもしれません。
Hibernate Validatorの拡張機能を使えば対応できる!
Hibernate Validationが提供している拡張機能を使うと、メッセージ定義から参照できる値を変更することができます。
private int value; // アノテーションの属性値(プロパティキー)からプロパティ値を格納
// ...
public boolean isValid(LocalDate targetValue, ConstraintValidatorContext context) {
boolean result = // チェック内容は省略...
// チェックNGの場合はデフォルト動作を無効化し、任意の制約違反情報を生成する
if (!result) {
// Hibernate Validatorの拡張機能を使うため、Hibernate Validator提供のインタフェースに変換
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
// デフォルトの制約違反情報の生成を無効化
hibernateContext.disableDefaultConstraintViolation();
// 任意の制約違反情報を生成
hibernateContext
// メッセージ定義で{value}で値を参照できるようにする
.addMessageParameter("days", this.days)
// メッセージ定義内のEL式でvalueという変数名で値を参照できるようにする
.addExpressionVariable("days", this.days)
// メッセージのテンプレートはアノテーションに指定しているテンプレートを使用する(デフォルト動作と同じ)
.buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate())
// 制約違反情報を追加する
.addConstraintViolation();
}
return result;
}
上記のような実装にすることで、メッセージにプロパティキーではなく、プロパティキーに対応する値を埋め込むことができるようになります。この仕組みを活用すると、メッセージの中に「チェックがOKとなる日付」を埋め込むこともできるようになります。
public boolean isValid(LocalDate targetValue, ConstraintValidatorContext context) {
LocalDate validMinDate = LocalDate.now().plusDays(value);
boolean result = // チェック内容は省略...
if (!result) {
HibernateConstraintValidatorContext hibernateContext =
context.unwrap(HibernateConstraintValidatorContext.class);
hibernateContext.disableDefaultConstraintViolation();
hibernateContext
.addMessageParameter("days", this.days)
.addExpressionVariable("days", this.days)
// チェックOKになる日付を追加
.addMessageParameter("validMinDate", validMinDate)
.addExpressionVariable("validMinDate", validMinDate)
.buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate())
.addConstraintViolation();
}
return result;
}
com.example.validation.DaysLater.message = must be {validMinDate} or later
制約違反した際のエラーメッセージは「must be 2018-09-26 or later」となり、より直感的なメッセージにすることができます。
まとめ
Hibernate Validator上で動かすことが前提条件となっているのであれば、今回紹介した拡張機能を使うことでユーザビリティの高いメッセージを組み立てやすくなると思われます。今携わっている案件はHivernate Validatorでの実行を前提として良いので、この仕組みを有効に活用しようと思っています。