Cubby 2.0 が OVal をサポートして入力検証をアノテーションでできるようになったということですが、 OVal を使ったことがなかったので触ってみました。バージョンは 1.31 です。
OVal - the object validation framework for Java™ 5 or later -
使ってみるとオブジェクトにアノテーションをつけて、インスタンスを validate するだけというシンプルなもので終わってしまいました。せっかくなので郵便番号の検証アノテーションを実装してみます。
今回作成する必要があるものは以下のものになりました。
まず ZipCode アノテーションを定義します。ここで実装ロジックのクラスを指定しますが、まだ存在しないためコンパイルエラーになります。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(checkWith = ZipCodeCheck.class)
public @interface ZipCode {
String format() default "^\\d{3}(?:\\-\\d{4})?$";
}
つづいて検証ロジックを実装します。アノテーションとお互いに依存しあっているため、アノテーションと同時に作成する必要があります。ロジックは AbstractAnnotationCheck クラスを継承します。
public class ZipCodeCheck extends AbstractAnnotationCheck<ZipCode> {
private static final long serialVersionUID = 1L;
private Pattern pattern;
@Override
public void configure(ZipCode constraintAnnotation) {
requireMessageVariablesRecreation();
pattern = Pattern.compile(constraintAnnotation.format());
}
public boolean isSatisfied(Object validatedObject, Object valueToValidate, OValContext context, Validator validator)
throws OValException {
if (!(valueToValidate instanceof String)) {
return false;
}
String value = (String) valueToValidate;
if (value == null) {
return true;
}
return pattern.matcher(value).find();
}
@Override
protected Map<String, String> createMessageVariables() {
Map<String, String> messageVariables = new HashMap<String, String>();
messageVariables.put("pattern", pattern.pattern());
return messageVariables;
}
}
指定したメッセージを出力するために createMessageVariables をオーバーライドし、メッセージで表示したいパラメーターを Map に登録します。 configure メソッドで requireMessageVariablesRecreation() を呼び出さないと createMessageVariables メソッドが呼ばれません。
カスタムメッセージを出力するプロパティファイル、ここでは messages.properties を作成します。キーは検証ロジック名の Check を取り除いた文字列 + .violated または 検証ロジック名 + .violated になります。
net.pshared.sandbox.oval.ZipCode.violated = {context} は郵便番号の形式になっていません。 :{pattern}
メッセージに含む {pattern} と createMessageVariables メソッドで返す Map の pattern が結びつきます。ファイルは作成せず configure メソッドで message プロパティに直接埋め込んでもよいみたいです。
エントリポイントを含むクラスです。
追加したプロパティファイルを登録し、後は対象インスタンスを validate することでエラー内容がリストで返ってきます。
public class OvalClient {
public static void main(String[] args) {
ResourceBundleMessageResolver resolver = (ResourceBundleMessageResolver) Validator.getMessageResolver();
resolver.addMessageBundle(ResourceBundle.getBundle("messages"));
Hoge h = new Hoge();
h.name = "hoge";
h.age = -1;
h.zipCode = "0";
h.date = new Date();
Validator validator = new Validator();
List<ConstraintViolation> list = validator.validate(h);
for (ConstraintViolation cv : list) {
FieldContext ctx = (FieldContext) cv.getContext();
System.out.println(ctx.getField().getName() + ":" + cv.getMessage());
}
}
private static class Hoge {
@NotBlank
@MaxLength(24)
public String name;
@NotNull
@NotNegative
public Integer age;
@ZipCode
public String zipCode;
public Date date;
}
}
age:net.pshared.sandbox.oval.OvalClient$Hoge.ageは負の数ではいけません。
zipCode:net.pshared.sandbox.oval.OvalClient$Hoge.zipCode は郵便番号の形式になっていません。 :^\d{3}(?:\-\d{4})?$
validate をかけた後にどのフィールドでエラーが発生したのかが ConstraintViolation から取得できず、 FieldContext から取得しています。
お試しで使っていましたがバリデーションフレームワークとして、かなり柔軟なものだと感じました。 Cubby で作っているアプリケーションでは、 OVal を使ってみようかなと思います。