~~
~~ Copyright 2004-2010 the Seasar Foundation and the Others.
~~
~~ Licensed under the Apache License, Version 2.0 (the "License");
~~ you may not use this file except in compliance with the License.
~~ You may obtain a copy of the License at
~~
~~     http://www.apache.org/licenses/LICENSE-2.0
~~
~~ Unless required by applicable law or agreed to in writing, software
~~ distributed under the License is distributed on an "AS IS" BASIS,
~~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
~~ either express or implied. See the License for the specific language
~~ governing permissions and limitations under the License.
~~

  --------
  バリデーション
  --------
  agata
  baba
  --------
  2009-06-29
  --------

{目次}

 *{{{#バリデーション}バリデーション}}

 *{{{#バリデーションの定義}バリデーションの定義}}

  *{{{#定義済みバリデーション}定義済みバリデーション}}

  *{{{#バリデーションのエラーメッセージ}バリデーションのエラーメッセージ}}

  *{{{#バリデーションルールのコピー}バリデーションルールのコピー}}

  *{{{#アクションメソッドからエラーページへの遷移}アクションメソッドからエラーページへの遷移}}

  *{{{#エラー発生時の挙動}エラー発生時の挙動}}

 *{{{#独自バリデーションの定義}独自バリデーションの定義}}

  *{{{#リクエストパラメータのバリデーション}リクエストパラメータのバリデーション}}

  *{{{#ユーザー定義のロジックによるバリデーション}ユーザー定義のロジックによるバリデーション}}

 *{{{#OVal_プラグイン}OVal プラグイン}}

  *{{{#OVal_プラグインの使用方法}OVal プラグインの使用方法}}

{バリデーション}

 Cubby ではフォームから入力された値をバリデーションする仕組みが用意されています。

 フォームから入力された値は HttpServletRequest#getParameterValues で返される値が String の配列であることからもわかるように、
 全ての値を配列として取得できます。定義済みのバリデーションはこの配列に対してのバリデーションを行います。

 また、フォームオブジェクトやデータベースを参照してデータ制約を検証する独自のバリデーションをプログラム上に定義することができます。

 バリデーションの実行タイミングは{{{action.html#リクエストからアクション実行までのフロー}リクエストからアクション実行までのフロー}}を参照してください。

{バリデーションの定義}

 バリデーションの定義は Java コードで記述します。以下のように、アクションメソッドに対してバリデーションのルールを定義します。

 [[1]]アクションに{{{cubby/apidocs/org/seasar/cubby/validator/ValidationRules.html}ValidationRules}}型のプロパティを用意し、
      定義したバリデーションのルールを取得できるようにします。

      以下のように{{{cubby/apidocs/org/seasar/cubby/validator/DefaultValidationRules.html}DefaultValidationRules}}を継承して、
      {{{cubby/apidocs/org/seasar/cubby/validator/DefaultValidationRules.html#initialize()}initialize}}メソッドをオーバーライドし、そのメソッドの中で
      {{{cubby/apidocs/org/seasar/cubby/validator/DefaultValidationRules.html#add(java.lang.String, org.seasar.cubby.validator.Validator...)}add}}メソッドを用いて
      フォームのフィールドごとにバリデーションのルールを追加します。


+------------------------------------------------------+
public class LoginAction extends Action {

  // バリデーションのルール
  public ValidationRules validationRules = new DefaultValidationRules() {
    @Override
    public void initialize() {
      // フィールド "userId" は必須入力で最大10文字まで
      add("userId", new RequiredValidator(), new MaxLengthValidator(10));

      // フィールド "password" は必須入力で最大10文字まで
      add("password", new RequiredValidator(), new MaxLengthValidator(10));
    }
  };
  ...
+------------------------------------------------------+

 [[2]]アクションメソッドにアノテーション{{{cubby/apidocs/org/seasar/cubby/action/Validation.html}@Validation}}を定義し、{{{cubby/apidocs/org/seasar/cubby/action/Validation.html#rules()}rules}}でアクションメソッドと[[1]]で定義したバリデーションルールのプロパティを関連付けます。

      入力検証でエラーを検出した場合は、アクションのプロパティ{{{cubby/apidocs/org/seasar/cubby/action/Action.html#errors}errors}}にメッセージが設定され、{{{cubby/apidocs/org/seasar/cubby/action/Validation.html#errorPage()}errorPage}}で指定されたエラーページへフォワードで遷移します。

+------------------------------------------------------+
  // プロパティ「validationRules」をバリデーションのルールとして使用します。
  // 検証エラー発生時は「confirm.jsp」に遷移します。
  @Validation(rules = "validationRules", errorPage = "confirm.jsp")
  public ActionResult save1() {
    ... 
  }

}
+------------------------------------------------------+

[]

*{定義済みバリデーション}

 Cubby に定義済みのバリデーションクラスは以下のものがあります。

 {{{cubby/apidocs/index.html?org/seasar/cubby/validator/ScalarFieldValidator.html}ScalarFieldValidator}} を実装しているクラスがひとつの要素を検証するバリデータ、
 {{{cubby/apidocs/index.html?org/seasar/cubby/validator/ArrayFieldValidator.html}ArrayFieldValidator}} を実装しているクラスが配列全体を検証するバリデータとなっています。

*----------------------+--------------------------------------+
|| クラス名            || 説明                                |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/ArrayMaxSizeValidator.html}ArrayMaxSizeValidator}}      |配列の最大サイズを検証します。            |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/ArrayMinSizeValidator.html}ArrayMinSizeValidator}}      |配列の最小サイズを検証します。            |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/DateFormatValidator.html}DateFormatValidator}}   |日付に対する検証を行います。              | 
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/EmailValidator.html}EmailValidator}}        |Eメールアドレスに対する検証を行います。    |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/EqualsValidator.html}EqualsValidator}}       |指定した文字列と等しいかどうかを検証します。|
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/FileRegexpValidator.html}FileRegexpValidator}}   |ファイルアップロードのファイル名が指定された正規表現にマッチするか検証します。|
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/MaxLengthValidator.html}MaxLengthValidator}}    |最大文字数を検証します。                 |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/NumberValidator.html}NumberValidator}}       |数値かどうかを検証します。                |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/RangeLengthValidator.html}RangeLengthValidator}}  |文字列の長さの範囲を指定して検証します。    |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/RangeValidator.html}RangeValidator}}       |数値の範囲を指定して検証します。           |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/RegexpValidator.html}RegexpValidator}}       |指定された正規表現にマッチするか検証します。|
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/RequiredValidator.html}RequiredValidator}}     |必須検証します。                         |
*----------------------+--------------------------------------+
|{{{cubby/apidocs/org/seasar/cubby/validator/validators/TokenValidator.html}TokenValidator}}     |2重サブミットを検証します。                         |
*----------------------+--------------------------------------+
定義済みバリデーションクラス一覧

 詳細は{{{cubby/apidocs/index.html?org/seasar/cubby/validator/validators/package-summary.html}APIドキュメント}}をご覧下さい。

*{バリデーションのエラーメッセージ}

 バリデーションのエラーメッセージは以下のルールで作成されます。

 * メッセージキーを指定しない場合、パラメータ名がエラーメッセージ中の項目名のメッセージキーとして使用されます。

 * メッセージキーを指定する場合、指定されたメッセージキーがエラーメッセージ中の項目名のメッセージキーとして使用されます。

 * DefaultValidationRules のコンストラクタでキーの項目名のメッセージキーのプリフィックスを指定することができます。

 * 各バリデーションのコンストラクタでメッセージキーを指定することで検証時のエラーメッセージを切り替えることもできます。

[]

+------------------------------------------------------+
public class LoginAction extends Action {

  // 項目のメッセージキーを指定
  public ValidationRules validation = new DefaultValidationRules() {
    @Override
    public void initialize() {
      // メッセージキー"userId"の値がエラーメッセージ中の項目名として使用されます。
      add("userId", new RequiredValidator(), new MaxLengthValidator(10));
      // メッセージキー"login.password"の値がエラーメッセージ中の項目名として使用されます。
      add("password", "login.password", new RequiredValidator(), new MaxLengthValidator(10));
    }
  };

  // 項目名のメッセージキーのプリフィックスを指定
  public ValidationRules validation2 = new DefaultValidationRules("login.") {
    @Override
    public void initialize() {
      // メッセージキー"login.userId"の値がエラーメッセージ中の項目名として使用されます。
      add("userId", new RequiredValidator(), new MaxLengthValidator(10));
    }
  };

  // 各バリデーションにメッセージキーを指定
  public ValidationRules validation3 = new DefaultValidationRules() {
    @Override
    public void initialize() {
      // メッセージキー"err.myrequired"がエラーメッセージとして使用されます。
      add("password", new RequiredValidator("err.myrequired"));
    }
  };

  ...
}
+------------------------------------------------------+

*{バリデーションルールのコピー}

 ほとんどが同じでいくつかの項目が異なる複数のルールを作りたい場合、以下のように他のルールをコピーして使用することができます。
 
+------------------------------------------------------+
public class LoginAction extends Action {

  // ベースのルール
  private ValidationRules baseValidationRules = new DefaultValidationRules() {
    @Override
    public void initialize() {
      add("userId", new RequiredValidator(), new MaxLengthValidator(10));
      add("password", "login.password", new RequiredValidator(), new MaxLengthValidator(10));
    }
  };

  // ベースのルールに「nickname」の検証を追加
  public ValidationRules validationRules1 = new DefaultValidationRules() {
    @Override
    public void initialize() {
      // baseValidationRulesのルールをコピーして追加
      addAll(baseValidationRules);
      add("nickname", new RequiredValidator(), new MaxLengthValidator(10));
    }
  };

  // ベースのルールに「password2」の検証を追加
  public ValidationRules validation2 = new DefaultValidationRules() {
    @Override
    public void initialize() {
      // baseValidationRulesのルールをコピーして追加
      addAll(baseValidationRules);
      add("password2", new RequiredValidator(), new MaxLengthValidator(10));
    }
  };
  ...
}
+------------------------------------------------------+

*{アクションメソッドからエラーページへの遷移}

 アクションメソッドでエラーを検出した場合は
 {{{cubby/apidocs/org/seasar/cubby/validator/DefaultValidationRules.html#ValidationException}ValidationException}}
 をスローすることで、処理を中断してエラーページへ遷移させることができます。

*{エラー発生時の挙動}

 DefaultValidationRules はバリデーションエラーが検出された場合にエラーページへフォワードします。
 この挙動は{{{cubby/apidocs/org/seasar/cubby/validator/DefaultValidationRules.html#fail(java.lang.String)}DefaultValidationRules.html#fail(java.lang.String)}}メソッドに定義されています。
 これをオーバーライドすることでエラー発生時の挙動を変更することができます。

+------------------------------------------------------+
  public ValidationRules validationRules = new DefaultValidationRules() {
    @Override
    public void initialize() {
      ...
    }

    @Override
    public void fail(String errorPage) {
      // クライアントに 404 Not Found を返す
      return new SendError(404);
    }
  };
+------------------------------------------------------+

{独自バリデーションの定義}

*{リクエストパラメータのバリデーション}

 リクエストパラメータのバリデーションを独自に作成するには{{{cubby/apidocs/org/seasar/cubby/validator/ScalarFieldValidator.html}ScalarFieldValidator}}
 または{{{cubby/apidocs/org/seasar/cubby/validator/ArrayFieldValidator.html}ArrayFieldValidator}}を実装して作成します。\
 実装の方法は{{{cubby/xref/org/seasar/cubby/validator/validators/package-summary.html}それぞれのインターフェイスの実装クラス}}を参照してください。

*{ユーザー作成のロジックによるバリデーション}

 データベースアクセスなどのユーザー作成のロジックによるバリデーションを作成するには{{{cubby/apidocs/org/seasar/cubby/validator/ValidationRule.html}ValidationRule}}を実装して作成してください。

 バリデーションルールを登録する際には {{{cubby/apidocs/org/seasar/cubby/validator/DefaultValidationRules.html#add(org.seasar.cubby.validator.ValidationPhase, org.seasar.cubby.validator.ValidationRule)}add}}
 メソッドの第一引数でバリデーションを実行するフェーズを指定できます。

 バリデーションはフェーズごとに実行されます。
 あるフェーズのバリデーションでエラーがあった場合は次のフェーズのバリデーションを実行せずにエラー処理へ移行します。

 デフォルトでは {{{cubby/apidocs/org/seasar/cubby/validator/DefaultValidationRules.html#DATA_TYPE}DATA_TYPE(データ型を検証するフェーズ)}} と
 {{{cubby/apidocs/org/seasar/cubby/validator/DefaultValidationRules.html#DATA_CONSTRAINT}DATA_CONSTRAINT(データ上の制約を検証するフェーズ)}} が定義されていて、
 フェーズを指定しない場合は DATA_TYPE のフェーズに追加されます。

+------------------------------------------------------+

  public UserDao userDao;

  public String userId;
  public String password;
  public Map<String, Object> sessionScope;

  public ValidationRules loginValidation = new DefaultValidationRules(
      "login.") {
    @Override
    public void initialize() {
      add("userId", new RequiredValidator());
      add("password", new RequiredValidator());
      // userId と password の双方が入力されたことが確認されてから UserValidationRule
      // を実行したいので、フェーズに DATA_CONSTRAINT に指定します。
      add(DATA_CONSTRAINT, new UserValidationRule());
    }
  };

  private class UserValidationRule implements ValidationRule {

    public void apply(Map<String, Object[]> params, Object form,
        ActionErrors errors) throws ValidationException {
      User user = userDao.findByIdAndPassword(userId, password);
      if (user == null) {
        // エラーを検出した時、次のバリデーションを実行しないようにするためには
        // ValidationException をスローしてください。
        throw new ValidationException("ユーザIDかパスワードが違います。", "userId", "password");
      }
      sessionScope.put("user", user);
    }

  }

+------------------------------------------------------+

{OVal プラグイン}

 Cubby のバリデーションはリクエストパラメータの名前に対するものなので、基本的にタイプセーフではありません。
 入力項目が多い場合など、制約をフィールドやメソッドへのアノテーションで記述するほうが便利なことも多いので、
 {{{http://oval.sourceforge.net/}OVal - the object validation framework}}
 と連携してアノテーションベースのバリデーションを行う Cubby OVal Plug-in を用意しています。

 このプラグインを使うと、リクエストパラメータがバインドされた後のフォームオブジェクトが OVal によって検証されます。

*{OVal プラグインの使用方法}

 [[1]]クラスパスに cubby-oval*.jar を追加してください。 

 [[2]]コンテナから
      {{{http://oval.sourceforge.net/api/net/sf/oval/localization/message/MessageResolver.html}net.sf.oval.localization.message.MessageResolver}} をキーに org.seasar.cubby.plugins.oval.validation.RequestLocaleMessageResolver を、
      {{http://oval.sourceforge.net/api/net/sf/oval/localization/context/OValContextRenderer.html}net.sf.oval.localization.context.OValContextRenderer}} をキーに org.seasar.cubby.plugins.oval.validation.RequestLocaleOValContextRenderer を、それぞれ取得できるように設定してください。

 [[3]]アクションクラスに ValidationRules を定義するとき org.seasar.cubby.plugins.oval.validation.OValValidationRule を追加してください。

 [[4]]フォームオブジェクトとなるクラスに OVal のアノテーションで制約を記述してください。

+------------------------------------------------------+
public class LoginAction extends Action {

  // バリデーションのルール
  public ValidationRules validationRules = new DefaultValidationRules() {
    @Override
    public void initialize(String resourceKeyPrefix) {
      //// フィールド "userId" は必須入力で最大10文字まで
      //add("userId", new RequiredValidator(), new MaxLengthValidator(10));
      //// フィールド "password" は必須入力で最大10文字まで
      //add("password", new RequiredValidator(), new MaxLengthValidator(10));
      add(new OValValidationRule(resourcekeyPrefix));
    }
  };

  @RequestParameter
  @NotNull
  @NotEmpty
  @MaxLength(10)
  private String userId;

  @RequestParameter
  @NotNull
  @NotEmpty
  @MaxLength(10);
  private String password

  @Validation(rules = "validationRules", errorPage = "confirm.jsp")
  public ActionResult save1() {
    ... 
  }
+------------------------------------------------------+

 