JSR303のアノテーションを利用する

Androidアプリケーション上でバリデーションの仕組みを構築するために、JSR 303:Beans Validationのアノテーションを利用してみよう、と以前に書いた。
AndroidのためのValidation

Beans Validationは一般的なバリデーションの制約として幾つかのアノテーションを提供している。
例えば、"NotNull"アノテーションは対象が非Nullであることを制約とするアノテーションだ。(ソースコードはJBossが公開する、"Hibernate Validator(http://www.hibernate.org/subprojects/validator.html)"のリポジトリからダウンロードできる"validation-api-1.0.0.GA"を使用している)

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
public @interface NotNull {
    String message() default {javax.validation.constraints.NotNull.message};
    Class groups() default { };
    Class payload() default {};

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        NotNull[] value();
    }
}

このまま使うのが筋なのだが、当面永続化のことは考えない(Hibernateはまず使わない)のと、Android向けということで極力シンプルな作りとしたいので、以下の属性は使わないこととする。

  • Class[] groups()

この制約に対するバリデーションが属するグループを指定する

  • Class[] payload()

この制約のペイロード(積載? 荷重?)を指定する

  • @interface List

この制約を複数指定する場合に使用する


また、@ConstraintアノテーションとそのvalidatedBy属性だが

@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Constraint {
    public Class>[] validatedBy();
}

中を見ると、制約バリデーションを実装するクラスをConstraintValidatorインタフェースの実装クラスに限定しているが、これも止めて単純にバリデーションを実施する型を指定する@ValidatorClassアノテーションを新規に作り、代わりに使用する。

  • ValidatorClass.java
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidatorClass {
    Class value();
}

最後にmessage()属性だがデフォルト値は、提供されているソースコードではプロパティのキーであり、リソースの外部化を実現する。

    String message() default {javax.validation.constraints.NotNull.message};

このように実装ではValidationMessages.properties等のローカライズ可能なJavaプロパティファイルとして定義しておき、実行時にリソースの文字列で置き換える。

  • ValidationMessages.properties
javax.validation.constraints.AssertFalse.message=must be false
javax.validation.constraints.AssertTrue.message=must be true
:
javax.validation.constraints.NotNull.message=may not be null
javax.validation.constraints.Null.message=must be null
javax.validation.constraints.Past.message=must be in the past
javax.validation.constraints.Pattern.message=must match "{regexp}"
javax.validation.constraints.Size.message=size must be between {min} and {max}
:

Androidは標準的なリソースには.propertiesは使わず、バイナリ化されるXMLを使用するので、

    int message() default R.string.NotNull;

と、int型に変更してstrings.xmlを作成、参照することで.properties同様に、外部化とローカライズに対応したい。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Beans Validation -->
    <string name="AssertFalse">must be false</string>
    <string name="AssertTrue">must be true</string>
    :
    <string name="NotNull">may not be null</string>
    <string name="Null">must be null</string>
    <string name="Past">must be in the past</string>
    <string name="Pattern">must match "{regexp}"</string>
    <string name="Size">size must be between {min} and {max}</string>
:
</resources>

strings.xmlはコンパイルすることでR.javaという名前のJavaソースコードに変換されて、他のコードと共にコンパイルされるため、実行前にエラーチェックを行うことができる。

public final class R {
    :
    public static final class string {
        public static final int NotNull=0x7f050001;
        :
    }
}

最終的イメージ

  • NotNull.java (改)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@ValidatorClass(NotNullValidator.class)
public @interface NotNull {
    int message() default R.string.NotNull;
}


次回はこれらのアノテーションを使ったバリデータの実装を考えてみようと思う。