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タグでエラーメッセージを表示する処理を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <?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値と画面遷移処理を定義したクラスの内容は以下の通りで、入力項目のチェック処理を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | 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(); } } |
さらに、共通ユーティリティクラスの内容は以下の通りで、入力項目のチェック処理で呼ばれるメソッドを定義している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 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)に設定している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?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の定義は以下の通りで、入力画面でエラーが発生した場合に画面遷移しない、という動作を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | <?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ファイルの内容は以下の通りで、エラーメッセージを赤字で表示する定義を追加している。
1 2 3 4 5 6 7 8 9 10 | .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タグを追加すればよい。