JSFのValidatorにおいて２回目のgetSubmittedValue()で値がnullになった時の対応方法

**環境**
OS : macOS Hight Sierra Version 10.13.2
Eclipse : Neon.3 Release (4.6.3)
Java : JDK8
GlassFish : 4.1.2

## 事象 : JSFのValidatorにおいて２回目のgetSubmittedValue()で値がnullになる
![スクリーンショット 2018-01-16 21.25.39.png](https://qiita-image-store.s3.amazonaws.com/0/159761/06b13a33-6cc2-3b23-c5eb-5cc4c6148f22.png)

```html:inputPassword.xhtml(画面)
<省略>
  <h:outputLabel>パスワードを入力して下さい。</h:outputLabel>
  <br />
  <h:inputSecret id="password" value="#{passwordBean.password}">
    <f:validateRequired />
    <f:validateLength minimum="3" maximum="10" />
    <f:validator validatorId="passwordValidator" />
    <f:attribute name="target" value="password" />
  </h:inputSecret>
  <h:message for="password" errorClass="error" />
  <br />
  <h:outputLabel>確認のためにもう&nbsp;&nbsp;１度入力して下さい。</h:outputLabel>
  <br />
  <h:inputSecret id="rePassword" value="#{passwordBean.kakuninPassword}">
    <f:validateRequired />
    <f:validator validatorId="passwordValidator" />
    <f:attribute name="target" value="rePassword" />
    <f:attribute name="passwordId" value="password" />
  </h:inputSecret>
  <h:message for="rePassword" errorClass="error" />
<省略>
```

```java:PasswordValidator.java(カスタムバリデータ)
<省略>
@FacesValidator(value = "passwordValidator")
public class PasswordValidator implements Validator {
    /** パスワードに使っちゃダメな文字列. */
    private static final String KINSHI = "password";
    /** チェック対象を指定するname属性の名前. */
    private static final String TARGET_KEY = "target";
    /** パスワードをチェックする場合に指定するname属性の値. */
    private static final String TARGET_PASSWORD = "password";
    /** 確認パスワードをチェックする場合に指定するname属性の値. */
    private static final String TARGET_RE_PASSWORD = "rePassword";
    /** パスワード入力欄のid. */
    private static final String PASSWORD_ID_KEY = "passwordId";

    /*
     * (non-Javadoc)
     * @see javax.faces.validator.Validator#validate(javax.faces.context.
     * FacesContext, javax.faces.component.UIComponent, java.lang.Object)
     */
    @Override
    public void validate(FacesContext context, UIComponent component, Object value)
            throws ValidatorException {
        String target = (String) component.getAttributes().get(TARGET_KEY);
        if (TARGET_PASSWORD.equals(target)) {
            validatePassword(value);
        }
        if (TARGET_RE_PASSWORD.equals(target)) {
            validateRePassword(component, value);
        }
    }

    /**
     * 入力されたパスワードを検証する.
     * @param value 入力値.
     */
    private void validatePassword(Object value) {
        String inputedValue = (String) value;
        if (inputedValue.equals(KINSHI)) {
            FacesMessage errorMessage = new FacesMessage(KINSHI + "は使えません。");
            errorMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
            throw new ValidatorException(errorMessage);
        }
    }

    /**
     * 確認用に再入力されたパスワードを検証する.
     * @param component {@link UIComponent}
     * @param value 入力値.
     */
    private void validateRePassword(UIComponent component, Object value) {
        String rePassword = (String) value;
        String passwordId = (String) component.getAttributes().get(PASSWORD_ID_KEY);
        if (StringUtils.isNotBlank(passwordId)) {
            String password = getInputedPasswordValue(component, passwordId);
            if (StringUtils.isNotEmpty(rePassword)) {
                if (!rePassword.equals(password)) {
                    FacesMessage errorMessage = new FacesMessage("入力されたパスワードと一致しません。");
                    errorMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
                    throw new ValidatorException(errorMessage);
                }
            }
        }
    }

    /**
     * inputタグのid属性値からパスワードの入力値を取得する.
     * @param component {@link UIComponent}
     * @param id inputタグのid属性値.
     * @return パスワードの入力値.
     */
    private String getPasswordValueById(UIComponent component, String id) {
        HtmlInputSecret inputTextConf = (HtmlInputSecret) component.findComponent(id);
        Object submittedValue = inputTextConf.getSubmittedValue();
        return submittedValue.toString();
    }
}
```

### ２回目に値がnullにならないパターン : 1回目の検証でエラー
![スクリーンショット 2018-01-16 21.42.57.png](https://qiita-image-store.s3.amazonaws.com/0/159761/87c52cc5-4459-12d8-6596-b060ffb97ca6.png)

![スクリーンショット 2018-01-16 21.43.24.png](https://qiita-image-store.s3.amazonaws.com/0/159761/284b4189-d8e0-4b9c-3970-b10b0cf194d2.png)

![スクリーンショット 2018-01-16 21.43.54.png](https://qiita-image-store.s3.amazonaws.com/0/159761/9f90dd40-e119-cf3f-4623-dace580e0da3.png)

![スクリーンショット 2018-01-16 21.44.25.png](https://qiita-image-store.s3.amazonaws.com/0/159761/52694d7c-e8c2-6635-6456-0a6e6e3ba22f.png)

### ２回目に値がnullになるパターン : 1回目の検証で正常
![スクリーンショット 2018-01-16 21.45.27.png](https://qiita-image-store.s3.amazonaws.com/0/159761/c4ed5308-1b14-cd29-4ffd-a5db3dbc0890.png)

![スクリーンショット 2018-01-16 21.45.51.png](https://qiita-image-store.s3.amazonaws.com/0/159761/2fe89dc7-ac95-15d8-711d-880c035f76f1.png)

![スクリーンショット 2018-01-16 21.46.46.png](https://qiita-image-store.s3.amazonaws.com/0/159761/75c59cfc-e8c5-237e-fbb6-1f5decfb0f3f.png)

## 理由 : 検証が終わってsetSubmittedValue(null)が実行されているから
> ![image.png](https://qiita-image-store.s3.amazonaws.com/0/159761/a757254c-09ec-355e-b694-f33d321b9263.png)
> **JSFのライフサイクル**
1. Viewの復元
2. 入力値の適用
3. 入力値の変換と検証
4. バッキングビーンの変数に値をバインド
5. 処理の実行
6. レスポンスのレンダリング

今回は、このライフサイクルの3で1回目の検証においてエラーになるとgetSubmittedValue()で値がnullになりました。

> [JSF 2.0 の詳細について | 寺田　佳央 - Yoshio Terada](https://yoshio3.com/2012/08/24/detail-of-jsf20/)
> ２回目以降のリクエスト（必要項目に記入してボタンを押下したような場合）では、UIView のツリーが復元（ライフサイクルの１番目）された後、リクエスト値の適用フェーズ（ライフサイクルの２番目）で UIComponent に対して入力値を適用します。(この例では HtmlInput#setSubmittedValue()が実行される。) この、setSubmittedValue() された値は、入力値の検証フェーズ(ライフサイクルの３番目) が完了するまで、getSubmittedValue() で値を取り出す事ができます。入力値の検証フェーズが終わると、入力値は、**setSubmittedValue(null)が実行**され、HtmlInput#setValue(“foo”) が呼び出され、**以降のフェーズでは入力値は HtmlInput#getValue() を利用して取得する**ようになります。

## 対応 : ２回目のgetSubmittedValue()で値がnullになったらgetValue()で値を取得する
```java
<他の箇所は修正なし>
    private String getPasswordValueById(UIComponent component, String id) {
        HtmlInputSecret inputTextConf = (HtmlInputSecret) component.findComponent(id);
        Object submittedValue = inputTextConf.getSubmittedValue();
        if (submittedValue == null) {
            // getSubmittedValue()で値がnullになったらgetValue()で値を取得する
            submittedValue = inputTextConf.getValue();
        }
        return submittedValue.toString();
    }
<他の箇所は修正なし>
```

![スクリーンショット 2018-01-16 22.48.50.png](https://qiita-image-store.s3.amazonaws.com/0/159761/a491f6d1-a9de-2e81-c3d0-d92015349dbf.png)

![スクリーンショット 2018-01-16 22.52.31.png](https://qiita-image-store.s3.amazonaws.com/0/159761/d9588ac2-bc4f-7cd8-436d-5183522cf45d.png)

![スクリーンショット 2018-01-16 22.52.43.png](https://qiita-image-store.s3.amazonaws.com/0/159761/a404d865-595b-f380-21a7-6f27ea45a33a.png)

![スクリーンショット 2018-01-16 22.53.01.png](https://qiita-image-store.s3.amazonaws.com/0/159761/1995353a-c591-b5c1-021a-a3fba578b424.png)

## 参考サイト：アノテーションでValidatorを仕掛ける時はこちらがさんこうになります
- [JSFのバリデーション - DENの思うこと](http://den2sn.hatenablog.com/entry/2014/12/22/092738)