JBoss Seamとは、Java EEベースで、JSF(JavaServer Faces)からEJB(Enterprise Java Bean) 3.0、JPA(Enterprise Java Bean)まで一貫したコンポーネントモデルで「つなぐ」Webアプリケーション開発用フレームワークで、いくつかのアノテーションを使うだけでEJBとJSFをつなぎ合わせることができるようになっている。
今回は、JBoss Seamアプリケーションでチェック処理を実装してみたので、そのサンプルプログラムを共有する。
前提条件
下記記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は、以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
入力画面(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.properties)自体は、Unicodeエスケープ形式で表示される。また、上記画面で、プロパティを選択し「編集」ボタンを押下すると、以下のように設定値を編集できるようになっている。
また、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
サンプルプログラムの実行結果
サンプルプログラムの実行結果は以下の通りで、チェック処理が正常に行えることが確認できる。
1) JBoss ASサーバーを起動し、「http://localhost:8082/demoSeam/」とアクセスすると、以下の画面が起動することが確認できる。
2) 何も入力せずに「確認」ボタンを押下すると、以下のように、エラーメッセージが表示される。
3) 名前と生年月日で必須入力チェック以外のエラーが出るよう入力し、「確認」ボタンを押下すると、以下のエラーメッセージが表示される。
4) 3)のエラーを解消すると、以下のように、確認画面に遷移することが確認できる。
要点まとめ
- JBoss Seamアプリケーションでチェックエラー時のメッセージを追加するには、FacesMessagesクラスのオブジェクトにエラーメッセージを追加する。
- JBoss Seamアプリケーションのエラーメッセージは、地域が日本の場合、messages_ja.propertiesというプロパティファイルに定義する。また、エラーメッセージ内に{0},{1},…という埋め込み文字を含めることができる。
- JBoss Seamアプリケーションのエラーメッセージを画面上に表示するには、h:messagesタグを追加すればよい。