JakartaEE(JavaEE)

JBoss Seamアプリケーションでチェック処理を実装してみた

JBoss Seamとは、Java EEベースで、JSF(JavaServer Faces)からEJB(Enterprise Java Bean) 3.0、JPA(Enterprise Java Bean)まで一貫したコンポーネントモデルで「つなぐ」Webアプリケーション開発用フレームワークで、いくつかのアノテーションを使うだけでEJBとJSFをつなぎ合わせることができるようになっている。

今回は、JBoss Seamアプリケーションでチェック処理を実装してみたので、そのサンプルプログラムを共有する。

前提条件

下記記事の実装が完了していること。

JBoss Seamアプリケーションで複数画面をもつWebアプリケーションを作成してみたJBoss Seamとは、Java EEベースで、JSF(JavaServer Faces)からEJB(Enterprise Java ...

サンプルプログラムの作成

作成したサンプルプログラムの構成は、以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。

入力画面(input.xhtml)の内容は以下の通りで、各項目にid属性を追加すると共に、h:messagesタグでエラーメッセージを表示する処理を追加している。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:a4j="http://richfaces.org/a4j">
<!-- h:head,h:bodyタグを使うとエラーになるため、head,bodyタグにそれぞれ変更 -->
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   <title>入力画面</title>
   <!-- cssファイルの読み込みをseams用に変更 -->
   <a4j:loadStyle src="resource:///stylesheet/demoJsf.css" />
</head>
<body>
   <p>下記必要事項を記載の上、「確認」ボタンを押下してください。</p><br/>
   <h:form>
     <h:messages errorClass="errorMessage" />  <!-- エラーメッセージを表示 -->
     <table border="0">
       <tr>
         <td align="left" valign="top">名前:</td>
         <td><h:inputText id="name" value="#{inputFormAction.name}" /></td>
       </tr>
       <tr>
         <td align="left" valign="top">生年月日:</td>
         <td>
           <h:inputText id="birthYear" value="#{inputFormAction.birthYear}" 
               size="4" maxlength="4" />年
           <h:selectOneMenu id="birthMonth" value="#{inputFormAction.birthMonth}">
             <f:selectItems value="#{inputFormAction.birthMonthItems}"/>
           </h:selectOneMenu>月 
           <h:selectOneMenu id="birthDay" value="#{inputFormAction.birthDay}">
             <f:selectItems value="#{inputFormAction.birthDayItems}"/>
           </h:selectOneMenu>日
         </td>
       </tr>
       <tr>
         <td align="left" valign="top">性別:</td>
         <td>
           <h:selectOneRadio id="sex" value="#{inputFormAction.sex}">
             <f:selectItems value="#{inputFormAction.sexItems}"/>
           </h:selectOneRadio>
         </td>
       </tr>
       <tr>
         <td align="left" valign="top">メモ:</td>
         <td><h:inputTextarea value="#{inputFormAction.memo}" 
             cols="40" rows="6" /></td>
       </tr>
       <tr>
         <td align="left" valign="top">入力確認:</td>
         <td><h:selectBooleanCheckbox id="checked" 
             value="#{inputFormAction.checked}" /></td>
       </tr>
     </table>
     <br/>
     <h:commandButton value="確認" action="#{inputFormAction.confirm()}" />
   </h:form>
</body>
</html>

また、各画面のForm値と画面遷移処理を定義したクラスの内容は以下の通りで、入力項目のチェック処理を追加している。

package faces;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.international.StatusMessage.Severity;

import common.CommonUtil;
import lombok.Data;
import lombok.ToString;

/**
 * 画面のフォーム値と画面遷移メソッドを定義.
 */
// 会話スコープを指定
// 会話スコープの開始・終了タイミングはpages.xmlで指定
@Scope(ScopeType.CONVERSATION)
// @Nameアノテーションは、JSFのXHTMLファイルから#{inputFormAction}で
// Javaクラスを参照できるようにしている(→バッキングビーン)
// ただし、org.jboss.seam.annotations.Nameクラスなので注意
@Name("inputFormAction")
@Data
@ToString(exclude={"birthMonthItems","birthDayItems","sexItems"})
public class InputFormAction implements Serializable {

    // シリアルバージョンUID
    private static final long serialVersionUID = 7283339629129432007L;

    /** 名前 */
    private String name;

    /** 生年月日_年 */
    private String birthYear;

    /** 生年月日_月 */
    private String birthMonth;

    /** 生年月日_日 */
    private String birthDay;

    /** 性別 */
    private String sex;

    /** 性別(ラベル) */
    private String sexLabel;

    /** メモ */
    private String memo;

    /** 確認チェック */
    private Boolean checked;

    /** 生年月日_月(選択リスト) */
    private List<SelectItem> birthMonthItems;

    /** 生年月日_日(選択リスト) */
    private List<SelectItem> birthDayItems;

    /** 性別(選択リスト) */
    private List<SelectItem> sexItems;

    /**
     * コンストラクタ生成時に選択リストの値を設定.
     */
    public InputFormAction(){
        // 生年月日_月(選択リスト)
        birthMonthItems = new ArrayList<SelectItem>();
        birthMonthItems.add(new SelectItem("", ""));
        for(Integer i = 1; i <= 12; i++){
            birthMonthItems.add(new SelectItem(String.valueOf(i), String.valueOf(i)));
        }

        // 生年月日_日(選択リスト)
        birthDayItems = new ArrayList<SelectItem>();
        birthDayItems.add(new SelectItem("", ""));
        for(Integer i = 1; i <= 31; i++){
            birthDayItems.add(new SelectItem(String.valueOf(i), String.valueOf(i)));
        }

        // 性別(選択リスト)
        sexItems = new ArrayList<SelectItem>();
        sexItems.add(new SelectItem(String.valueOf(1),"男"));
        sexItems.add(new SelectItem(String.valueOf(2),"女"));
    }

    /**
     * 確認画面への遷移.
     * @return 確認画面へのパス
     */
    public String confirm(){
        // 入力画面で入力チェックエラーがある場合は、画面遷移せずエラーメッセージを表示
        if(!this.validateInput()){
            return "chkerror";
        }

        // 性別(ラベル)を設定
        if(!CommonUtil.isBlank(sex)){
            this.setSexLabel(this.getSexItems().get(
                Integer.parseInt(this.getSex())-1).getLabel());
        }
        // Formに設定された値を出力
        System.out.println(this.toString());

        // 確認画面に遷移
        return "confirm";
    }

    /**
     * 入力画面に戻る.
     * @return 入力画面へのパス
     */
    public String back(){
        // 入力画面に戻る
        return "back";
    }

    /**
     * 完了画面への遷移.
     * @return 完了画面へのパス
     */
    public String send(){
        // 確認画面に表示された値を出力
        System.out.println(this.toString());

        // 完了画面への遷移
        return "send";
    }
    
    /**
     * 入力項目のチェック処理
     * @return チェック結果
     */
    private boolean validateInput(){
        // 戻り値として返す値
        boolean retVal = true;

        // FacesContext、UIComponentを取得
        FacesContext context = FacesContext.getCurrentInstance();
        UIComponent component = context.getViewRoot().getChildren().get(1);

        // 名前を取得する
        UIInput nameUI = (UIInput)component.findComponent("name");
        String nameSt = (String)nameUI.getValue();

        // 生年月日の年・月・日を取得する
        UIInput birthYearUI = (UIInput)component.findComponent("birthYear");
        UIInput birthMonthUI = (UIInput)component.findComponent("birthMonth");
        UIInput birthDayUI = (UIInput)component.findComponent("birthDay");
        String birthYearSt = (String)birthYearUI.getValue();
        String birthMonthSt = (String)birthMonthUI.getValue();
        String birthDaySt = (String)birthDayUI.getValue();

        // 性別を取得する
        UIInput sexUI = (UIInput)component.findComponent("sex");
        String sexSt = (String)sexUI.getValue();

        // 入力確認チェックの値を取得する
        UIInput checkedUI = (UIInput)component.findComponent("checked");
        Boolean checkedBl = (Boolean)checkedUI.getValue();

        // 名前が空白値の場合はエラーメッセージを設定する
        if(CommonUtil.isBlank(nameSt)){
            addErrorMessage("object.empty.message", "名前");
            retVal = false;

        } else {
            // 名前が1文字以上10文字以下でない場合はエラーメッセージを設定する
            if(!(nameSt.length() >= 1 && nameSt.length() <= 10)) {
                addErrorMessage("name.length.message", "名前", "1", "10");
                retVal = false;
            }
        }

        // 生年月日の年・月・日がすべて空白値の場合はエラーメッセージを設定する
        if (CommonUtil.isBlank(birthYearSt) && CommonUtil.isBlank(birthMonthSt)
                && CommonUtil.isBlank(birthDaySt)) {
            addErrorMessage("object.empty.message", "生年月日");
            retVal = false;

        } else {
            // 生年月日が存在しない日付の場合はエラーメッセージを返す
            String dateStr = birthYearSt + CommonUtil.addZero(birthMonthSt) 
                + CommonUtil.addZero(birthDaySt);
            if(!CommonUtil.isCorrectDate(dateStr, "yyyyMMdd")){
                addErrorMessage("birthday.invalid.message");
                retVal = false;
            }
        }

        // 性別が空白値の場合はエラーメッセージを設定する
        if(CommonUtil.isBlank(sexSt)){
            addErrorMessage("object.empty.message", "性別");
            retVal = false;
        }
 
        // 入力確認チェックが未指定の場合はエラーメッセージを返す
        if(!checkedBl.booleanValue()){
            addErrorMessage("checked.notchecked.message");
            retVal = false;
        }

        return retVal;
    }

    /**
     * 引数のメッセージKeyをもつエラーメッセージを追加.
     * @param messageKey メッセージKey
     * @param params メッセージの埋め込み文字
     */
    private void addErrorMessage(String messageKey, Object... params){
        FacesMessages facesMessages 
            = (FacesMessages) Component.getInstance("facesMessages");
        facesMessages.addFromResourceBundle(Severity.ERROR, messageKey, params);
        FacesContext.getCurrentInstance().renderResponse();
    }
}

さらに、共通ユーティリティクラスの内容は以下の通りで、入力項目のチェック処理で呼ばれるメソッドを定義している。

package common;

import java.text.SimpleDateFormat;

/**
 * 共通ユーティリティクラス.
 */
public class CommonUtil {

    /**
     * 引数で指定した文字列がNULL,空文字,空白のみかどうかをチェックする.
     * @param cs 文字列
     * @return 判定結果
     */
    public static boolean isBlank(final CharSequence cs) {
        final int strLen = length(cs);
        if (strLen == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * 引数で指定した文字列の長さを返却する.
     * @param cs 文字列
     * @return 文字列の長さ
     */
    public static int length(final CharSequence cs) {
        return cs == null ? 0 : cs.length();
    }

    /**
     * SimpleDateFormatを利用して日付チェックを行う.
     * @param dateStr チェック対象文字列
     * @param dateFormat 日付フォーマット
     * @return 日付チェック結果
     */
    public static boolean isCorrectDate(String dateStr, String dateFormat){
        if(CommonUtil.isBlank(dateStr) || CommonUtil.isBlank(dateFormat)){
            return false;
        }
        // JDK6なので、SimpleDateFormatクラスを利用して日付を変換する
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        sdf.setLenient(false);
        try {
            // チェック対象文字列をDate型の日付に変換できれば、チェックOKとする
            sdf.parse(dateStr);
            return true;
        } catch(Exception e) {
            return false;
        }
    }

    /**
     * 数値文字列が1桁の場合、頭に0を付けて返す.
     * @param intNum 数値文字列
     * @return 変換後数値文字列
     */
    public static String addZero(String intNum){
        if(CommonUtil.isBlank(intNum)){
            return intNum;
        }
        if(intNum.length() == 1){
            return "0" + intNum;
        }
        return intNum;
    }

}

また、メッセージを定義したプロパティ(messages_ja.properties)に設定した内容は以下の通りで、エラーメッセージを定義している。
messages_ja_1

なお、プロパティ(messages_ja.properties)自体は、Unicodeエスケープ形式で表示される。また、上記画面で、プロパティを選択し「編集」ボタンを押下すると、以下のように設定値を編集できるようになっている。
messages_ja_2

また、faces-config.xmlの内容は以下の通りで、デフォルトの地域を日本(ja)に設定している。

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
    version="1.2">
	<application>
        <view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
        <locale-config>
            <default-locale>ja</default-locale>
            <supported-locale>en</supported-locale>
            <supported-locale>bg</supported-locale>
            <supported-locale>de</supported-locale>
            <supported-locale>en</supported-locale>
            <supported-locale>fr</supported-locale>
            <supported-locale>tr</supported-locale>
        </locale-config>
    </application>

</faces-config>

さらに、画面遷移を定義したpages.xmlの定義は以下の通りで、入力画面でエラーが発生した場合に画面遷移しない、という動作を追加している。

<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd">
    
    <!-- 入力画面から確認画面への遷移 -->
    <page view-id="/input.xhtml">
        <!-- 確認画面に遷移する直前に、会話スコープを開始 -->
        <!-- 既に会話スコープを開始している場合は、その会話スコープをそのまま使う -->
        <begin-conversation join="true"/>
        <navigation from-action="#{inputFormAction.confirm()}">
            <rule if-outcome="confirm">
                <redirect view-id="/confirm.xhtml"/>
            </rule>
            <!-- 入力画面でエラーが発生した場合は、画面遷移しない -->
            <rule if-outcome="chkerror">
                <redirect view-id="/input.xhtml"/>
            </rule>
        </navigation>
    </page>
    
    <!-- 確認画面から完了画面・入力画面への遷移 -->
    <page view-id="/confirm.xhtml">
        <navigation from-action="#{inputFormAction.send()}">
            <rule if-outcome="send">
                <!-- 完了画面にリダイレクト遷移する直前に、会話スコープを終了 -->
                <end-conversation before-redirect="true"/>
                <redirect view-id="/complete.xhtml"/>
            </rule>
        </navigation>
        <navigation from-action="#{inputFormAction.back()}">
            <rule if-outcome="back">
                <redirect view-id="/input.xhtml"/>
            </rule>
        </navigation>
    </page>
    
    <!-- 以下はSeamプロジェクト作成時に記載された内容となる -->
    <exception class="org.jboss.seam.framework.EntityNotFoundException">
        <redirect view-id="/error.xhtml">
            <message severity="warn">Record not found</message>
        </redirect>
    </exception>
    
    <exception class="javax.persistence.EntityNotFoundException">
        <redirect view-id="/error.xhtml">
            <message severity="warn">Record not found</message>
        </redirect>
    </exception>
    
    <exception class="javax.persistence.EntityExistsException">
        <redirect view-id="/error.xhtml">
            <message severity="warn">Duplicate record</message>
        </redirect>
    </exception>
    
    <exception class="javax.persistence.OptimisticLockException">
        <end-conversation/>
        <redirect view-id="/error.xhtml">
            <message severity="warn">Another user changed the same data, please try again</message>
        </redirect>
    </exception>
    
    <exception class="org.jboss.seam.security.AuthorizationException">
        <redirect view-id="/error.xhtml">
            <message severity="error">You don't have permission to access this resource</message>
        </redirect>
    </exception>
    
    <exception class="org.jboss.seam.security.NotLoggedInException">
        <redirect view-id="/login.xhtml">
            <message severity="warn">#{messages['org.jboss.seam.NotLoggedIn']}</message>
        </redirect>
    </exception>
    
    <exception class="javax.faces.application.ViewExpiredException">
        <redirect view-id="/error.xhtml">
            <message severity="warn">Your session has timed out, please try again</message>
        </redirect>
    </exception>
    
    <exception class="org.jboss.seam.ConcurrentRequestTimeoutException" log-level="trace">
      <http-error error-code="503" />
    </exception>
     
    <exception>
        <redirect view-id="/error.xhtml">
            <message severity="error">Unexpected error, please try again</message>
        </redirect>
    </exception>
    
</pages>

その他、CSSファイルの内容は以下の通りで、エラーメッセージを赤字で表示する定義を追加している。

.lineBreakFormat{
    /* 改行コードをbrタグに変換し表示する */
    /* 空白文字はそのまま残す */
    white-space: pre;
}
.errorMessage{
    /* エラーメッセージを赤字で表示する */
    /* color: #FF0000; */
    color: red;
}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/seam-web-check/demoSeam



削除または保存していないWordドキュメントの復元方法【4DDiG Windowsデータ復元】ワード(Word)データ等のファイルを誤って削除してしまった場合は、通常はデータの復元ができませんが、4DDiGというソフトウェアを利用...

サンプルプログラムの実行結果

サンプルプログラムの実行結果は以下の通りで、チェック処理が正常に行えることが確認できる。

1) JBoss ASサーバーを起動し、「http://localhost:8082/demoSeam/」とアクセスすると、以下の画面が起動することが確認できる。
サンプルプログラムの実行結果_1

2) 何も入力せずに「確認」ボタンを押下すると、以下のように、エラーメッセージが表示される。
サンプルプログラムの実行結果_2_1

サンプルプログラムの実行結果_2_2

3) 名前と生年月日で必須入力チェック以外のエラーが出るよう入力し、「確認」ボタンを押下すると、以下のエラーメッセージが表示される。
サンプルプログラムの実行結果_3_1

サンプルプログラムの実行結果_3_2

4) 3)のエラーを解消すると、以下のように、確認画面に遷移することが確認できる。
サンプルプログラムの実行結果_4_1

サンプルプログラムの実行結果_4_2

要点まとめ

  • JBoss Seamアプリケーションでチェックエラー時のメッセージを追加するには、FacesMessagesクラスのオブジェクトにエラーメッセージを追加する。
  • JBoss Seamアプリケーションのエラーメッセージは、地域が日本の場合、messages_ja.propertiesというプロパティファイルに定義する。また、エラーメッセージ内に{0},{1},…という埋め込み文字を含めることができる。
  • JBoss Seamアプリケーションのエラーメッセージを画面上に表示するには、h:messagesタグを追加すればよい。