トップ «前の日記(2009-06-13) 最新 次の日記(2009-06-15)» 編集

Ussy Diary


2009-06-14

[Java]OVal

Cubby 2.0 が OVal をサポートして入力検証をアノテーションでできるようになったということですが、 OVal を使ったことがなかったので触ってみました。バージョンは 1.31 です。

OVal - the object validation framework for Java™ 5 or later -

使ってみるとオブジェクトにアノテーションをつけて、インスタンスを validate するだけというシンプルなもので終わってしまいました。せっかくなので郵便番号の検証アノテーションを実装してみます。

今回作成する必要があるものは以下のものになりました。

  • アノテーション定義
  • 検証ロジック
  • カスタムメッセージ定義

ZipCode.java

まず ZipCode アノテーションを定義します。ここで実装ロジックのクラスを指定しますが、まだ存在しないためコンパイルエラーになります。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(checkWith = ZipCodeCheck.class)
public @interface ZipCode {

    String format() default "^\\d{3}(?:\\-\\d{4})?$";
}

ZipCodeCheck.java

つづいて検証ロジックを実装します。アノテーションとお互いに依存しあっているため、アノテーションと同時に作成する必要があります。ロジックは 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

カスタムメッセージを出力するプロパティファイル、ここでは messages.properties を作成します。キーは検証ロジック名の Check を取り除いた文字列 + .violated または 検証ロジック名 + .violated になります。

net.pshared.sandbox.oval.ZipCode.violated = {context} は郵便番号の形式になっていません。 :{pattern}

メッセージに含む {pattern} と createMessageVariables メソッドで返す Map の pattern が結びつきます。ファイルは作成せず configure メソッドで message プロパティに直接埋め込んでもよいみたいです。

OvalClient.java

エントリポイントを含むクラスです。

追加したプロパティファイルを登録し、後は対象インスタンスを 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 を使ってみようかなと思います。