Teedaのサンプルアプリを読んでみる(2)

次はlogin.htmlに対応するクラスLoginPage。最初のDUMMY_CHECK_VALUEはさらっとスルーして、userの直前にある

@Required
@Length(minimum = 3)

は、TigerアノテーションによるValidatorの指定。Validatorが起動する時点で、このインスタンスのプロパティuser/passには、ブラウザの同名idを持つコンポーネントへ入力された値がセットされている。自分でgetParameter()しなくていいので楽チンだ。

Teeda入門記のValidationは定数アノテーションの解説しかないけど*1、最初のが必須入力、次が3文字以上入力されていることをチェックするもの、というのは大体想像がつくし、実際そのように働く。このルールに反した入力があると、自動的に自画面へ遷移し(というか戻り)、この「プロパティ名(=HTML上のid名)+"Message"」というidを持つoutputTextコンポーネント(早い話がただのspanタグ)がエラーメッセージで置換される。たとえばユーザIDを入力せず[ログイン]を押下すると

値を入力してください(ユーザ名)

が入力欄の右側に表示される。なるほど。しかし()内の「ユーザ名」というのはどこから来たんだ?login.html上にはそれらしい情報はない(labelタグを削除しても変化無し)。いろいろ探したところ、この「ユーザ名」をUnicodeコードに変換したもの(=\u30e6\u30fc\u30b6\u540d)がサブアプリケーション内のlabel_ja.properties(/web/todo/label_ja.properties)に含まれているのを発見。以下がそれ。

login.user=\u30e6\u30fc\u30b6\u540d

labelに対するメッセージの定義ルールから見ると、最初のloginがhtml名で、userはid。一方@Requiredに対応するメッセージID(キー)はjavax.faces.Messages_ja.propertiesに定義されているavax.faces.component.UIInput.REQUIRED_detailと思われるが、そこでは次のようになっている。

javax.faces.component.UIInput.REQUIRED_detail = \u5024\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044({0})

Unicode部分は「値を入力してください」なので、この"{0}"がlabel_ja.propertiesに定義されている対応するコンポーネント名(=login.user)の値に置換される、という感じなんだろう。ためしにlogin.userの値を変更すると()内に表示される内容が変わる。

さらにこのメッセージそのものを変更するにはどうすればいいか?MLなどを見るとfaces-config.xmlで以下のように定義されているので、このappMessages_ja.propertiesへメッセージを登録すればいいらしい。

<application>
	<message-bundle>appMessages</message-bundle>
	<locale-config>
		<default-locale>en</default-locale>
		<supported-locale>ja</supported-locale>
	</locale-config>
</application>

たとえば@Requiredのエラーメッセージを「コンポーネント名+"を入力してください。"」と表示させるには、上記appMessages_ja.propertiesに次の1行を追加する。

javax.faces.component.UIInput.REQUIRED={0}\u304c\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002

javax.faces.Messages_ja.propertiesはキーに"_detail"があるけど、それを上記のように除かないとうまくメッセージが変更できない。どういう仕様によるものかはわからないけど、一応変更できそうというのは確認できたので良しとしましょう。あとでValidatorの追加とかも調べよっと。

passはuser同様なのでスルー。setter/getterもスルー。

次のdoLogin()はlogin.htmlの[ ログイン ]が押下されたとき起動されるメソッド。idが"do"で始まっている場合、対応するPageクラスの同名メソッドが起動されるというTeedaの仕様により、自動的にこのメソッドに制御が渡される(それまでにValidationエラーなどが発生していなければ)。

public Class doLogin() {
	if (DUMMY_CHECK_VALUE.equals(getUser())
			&& DUMMY_CHECK_VALUE.equals(pass)) {
		setSessionAttribute(TodoConstant.SESSION_BIND_LOGIN, new Boolean(
				true));
		return TodoListPage.class;
	} else {
		setSessionAttribute(TodoConstant.SESSION_BIND_LOGIN, new Boolean(
				false));
		FacesMessageUtil.addErrorMessage("E0000003");
	}
	return null;
}

3行目のpassはgetPass()だろ、というのは置いといて、ここではuser/passとも"teeda"(=DUMMY_CHECK_VALUE)と入力されているかどうかcheckして、その結果に応じて次の遷移先を決定する。この中でsetSessionAttribute()は、このクラスPageの基底クラスであるAbstractTodoPageで定義されているもの。AbstractTodoPageを見るとヘルパーメソッドのようだけど、このクラスはsessionというプロパティを持っているので、そこにHttpSessionがDIされる。ただあれだね。PageをAbstractTodoPageのサブクラスにしたのは、このsetSessionAttribute()を使いたいから、という感じ。それ以外に何か意図があったのかな。あるとすればTodoというサブアプリケーション単位に共通する処理をAbstractTodoPageに定義しておいて、各Pageは底から派生させるスタイルなのか。うーん、コードから読み解くのは難しいな。

まぁそれも置いといて、"do"で始まるメソッドは戻り値で遷移先を決定する。check OKならTodoListPage.classを返すことにより(TodoListPageに対応するhtmlである)todoList.htmlへ遷移させる。NGならエラーメッセージを登録する。

FacesMessageUtil.addErrorMessage("E0000003");

引数"E0000003"はappMessages_ja.propertiesで定義*2されているメッセージID(キー)。ブラウザ上にはこれに対応するメッセージが表示されるわけだが、これははid属性を持ったコンポーネントに(直接)紐づいていないので、

<span id="messages" class="err"/>

の方へ表示される。なおaddErrorMessage()には、メッセージに埋め込む引数をとるのも用意されている。

public static void addErrorMessage(String messageId, Object[] args)

そしてnullを返すことで自画面を再表示する。JSPスクリプトレットを使って前回の入力値を取り出し、再構成する必要が無いので楽だ。

*1Tigerアノテーションと定数アノテーションの対応表はJava Expert#01に掲載されている。

*2:faces-config.xmlで指定されたメッセージファイル