もっと本格的なイベント処理については、本稿の最後のほうで説明する。
マネージドビーン作成機能
JSFでは、Webアプリケーションの各部位を簡単にビーンのプロパティにできる。たとえば:
<h:input_secret valueRef='loginForm.password'/>
上のタグは、Webページ上で入力される値をloginFormというビーンのpasswordプロパティに結び付けている。以前のJSFでは、入力部位そのものをビーンのプロパティに結び付けた(属性名はvalueRefではなくmodelReferenceだった)が、デベロッパは<jsp:useBean>を使ってビーンを作成/指定しなければならなかった。
EA4では、ビーンの作成についてはJSFが面倒見るから、デベロッパはビーンをコンフィギュレーションファイルの中で指定するだけでよい。コンフィギュレーションファイルの中では、ビーンのプロパティや、複数のビーンのランタイム時の関係も指定できる。実に便利なコンフィギュレーションファイルなのだ!。
ポートレットとの互換性
JSFのEA4では、ポートレットの規格を重視し、そのために、サーブレットのAPIを直接呼び出さないようになった。たとえばEA1では、FacesContext.getServletContext()という呼び出しでサーブレットコンテキストの参照(リファレンス)を入手していたが、ポートレットではポートレットコンテキストの参照が必要だ。
そこでEA4にはもはや、FacesContext.getServletContext()という呼び出しはない。その代わりに、FacesContext.getExternalContext()を呼び出すのだ。ExternalContextクラスは、いろんなコンテキスト(通常はサーブレットコンテキストまたはポートレットコンテキスト)に対するラッパー(wrapper,包み役)だ。
ExternalContextを無指定で使うと、一般的にコンテナ(Webサーバアプリケーションのエンジンコンテナ)のコンテキストにアクセスできる。たとえばExternalContext.getRequest()という呼び出しは、ServletRequestのインスタンスまたはPortletRequestのインスタンス、どちらかを指すObjectの参照を返す。このObjectを、適切なものにキャストすればよい。
コンテキストを、ServletContextかPortletContextのどちらかに特定したいときには、ExternalContext.getContext()を呼び出し、その返し値を正しくキャストする。
フレームワークの拡張
さらにJSFのEA4では、デベロッパがオブジェクトをプラグインすることによって、JSFフレームワークそのものを拡張したり部分変更できるようになった。それらのオブジェクトとは、以下のものだ:
・ナビゲーションハンドラ
・デフォルトのアクションリスナ
・プロパティリゾルバ
・変数リゾルバ
・ビューハンドラ
これらのオブジェクトを、デフォルトのものから変えたくなる理由が、いろいろある。たとえば、ナビゲーションハンドラが、アクションの結果に基づいてリクエストを別のリソースに転送(forward)するとき、必ずセキュリティのチェックをしたい…そんなときには、あなたの特製のハンドラが必要だ。また、値を参照し値を決定するためのオブジェクトを特製して、それをJSFに値参照のために使わせたい、という場合がある…そんなときには、変数リゾルバを自作するとよい。これらの自作クラス(仮にその名前がAcmeNavigationHandlerとAcmeVariableResolverだとしよう)はもちろん、コンフィギュレーションファイルの中で登録する。
旧と新
以上で、JSFの新機能の理論的な説明はひとまず終わりだ。では次に、私が以前EA1のために書いたアプリケーションを、EA4向けに書き換えるとどうなるか、という例を見ていこう。以前の最初の記事では、図1のような簡単なログインアプリケーションについて説明した。
図1:JSFで作成した簡単なログインページ
(画像は原文上に)
図1のアプリケーションは、あなたの予想どおりの動作をする:まず入力を検査し、正しければウェルカムページへリクエストを転送する。正しくなければ、エラーメッセージと共にログインページを再表示する。このアプリケーションはJSFの標準の部位とヴァリデータを使っている。では、このアプリケーションの最初の実装と、最新の実装を、比較してみよう。
JSFのEA1で、デベロッパを最も苦しめたのが、アプリケーションハンドラだ。どのJSFアプリケーションでも、アプリケーションハンドラという形で、イベント処理とナビゲーションを記述しなければならなかった。私の最初の記事では、次のようなアプリケーションハンドラを実装した:
コード1:JSF EA1のころのアプリケーションハンドラ
package com.sabreware.appHandlers;
import javax.faces.FactoryFinder;
import javax.faces.context.FacesContext;
import javax.faces.event.FacesEvent;
import javax.faces.lifecycle.ApplicationHandler;
import javax.faces.tree.TreeFactory;
public class SimpleApplicationHandler implements ApplicationHandler {
public boolean processEvent(FacesContext context, FacesEvent facesEvent) {
TreeFactory treeFactory = (TreeFactory)FactoryFinder.
getFactory(FactoryFinder.TREE_FACTORY);
context.setResponseTree(
treeFactory.getTree(context.getServletContext(),
"/welcome.jsp"));
return true;
}
}
コード1のアプリケーションハンドラは、すべてのリクエストを/welcome.jspへ転送する。だから、図1のLog Inボタンをクリックすると、ヴァリデーションエラーがなければ必ず/welcome.jspへ行く。このアプリケーションハンドラのほかに、EA1では、デベロッパがサーブレットコンテキストリスナを実装して、アプリケーションハンドラをインストールしなければならなかった。下に示すのが、そのサーブレットコンテキストリスナのコードだ:
コード2:JSF EA1で必要だったサーブレットコンテキストリスナ
package com.sabreware.listeners;
import javax.servlet.*;
import javax.faces.*;
import javax.faces.lifecycle.*;
import com.sabreware.appHandlers.SimpleApplicationHandler;
public class SimpleServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent e) {
LifecycleFactory factory = (LifecycleFactory)
FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
Lifecycle lifecycle = factory.getLifecycle(
LifecycleFactory.DEFAULT_LIFECYCLE);
lifecycle.setApplicationHandler(new SimpleApplicationHandler());
}
public void contextDestroyed(ServletContextEvent e) {
// Nothing to do here
}
}
そして最後に、コード2のコンテキストリスナを、次のように、/WEB-INF/web.xmlに登録しなければならなかった:
コード3:サーブレットコンテキストリスナを/WEB-INF/web.xmlに登録する
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Context Listener creates and sets the application handler -->
<listener>
<listener-class>
com.sabreware.listeners.SimpleServletContextListener
</listener-class>
</listener>
...
</web-app>
たかだか、/welcom.jspへ行くために、これだけものすごい遠回りをしなければならなかった!。しかしJSF EA4では、ファクトリーとかなんとかの、面倒くさいオブジェクトをデベロッパは相手にしなくてよい。上の、コード1から3までの処理を、EA4では次のように書く:
コード4:ナビゲーションルールをJavaではなくXMLで書く
<navigation-rule>
<navigation-case>
<description>All JSF requests (there's only one request in
this application) are directed to /welcome.jsp</description>
<to-tree-id>/welcome.jsp</to-tree-id>
</navigation-case>
</navigation-rule>
上のコード4は、コンフィギュレーションファイルの中に書くXMLだ。たったこれだけで、上のコード1から3までを表現している。つまり、すべてのリクエストが/welcome.jspへ行く、ということを。
EA1では、Javaのソースファイルが2つ、そしてweb.xmlの中の記述が1つ必要だった。EA4では、たった6行のXMLだけだ。しかも、後で/welcome.jspを/start.jspに変更したくなったとき、Javaのソースファイルを書き換えて再コンパイルする必要もない。
EA1のアプリケーションハンドラでは、ナビゲーションの処理だけでなく、イベント処理も書く必要があった。図1のログインアプリケーションでは、イベントはたった一つだった(Log Inボタンが押された、というアクションイベント)。しかし現実のアプリケーションでは、デベロッパの頭が混乱するような長いswitch文やif/else文を使って、多数のイベントを正しくさばかなければならない。たとえば、こんなふうに:
[醜悪なアプリケーションハンドラ]
public class UglyApplicationHandler implements ApplicationHandler {
public boolean processEvent(FacesContext context, FacesEvent facesEvent) {
if(facesEvent instanceof CommandEvent) {
if("loginForm.login".equals(facesEvent.())) {
// Code for logging in
}
else if("loginForm.cancel".equals(facesEvent.())) {
// Code for canceling the login form
}
else if("loginForm.reset".equals(facesEvent.())) {
// Code for resetting the login form
}
if("registerForm.register".equals(facesEvent.())) {
// Code for registering
}
else if("registerForm.cancel".equals(facesEvent.())) {
// Code for canceling the registration form
}
...
}
TreeFactory treeFactory = (TreeFactory)FactoryFinder.
getFactory(FactoryFinder.TREE_FACTORY);
if(someConditionSetInCodeAbove) {
context.setResponseTree(
treeFactory.getTree(context.getServletContext(),
"/somewhere.jsp"));
}
else if(someOtherConditionSetInCodeAbove) {
context.setResponseTree(
treeFactory.getTree(context.getServletContext(),
"/somewhere-else.jsp"));
}
...
return true;
}
}
長い長いswitch文をメンテナンスすることは、誰もがやりたがらない。そこで、助っ人としてCommandパターンが登場した。このパターンは、switch文の各caseをオブジェクトにして扱う。SwingやStrutsなどのユーザインタフェイスフレームワークでよく使われていたが、ついにJavaServer Faces(JSF)でも使われるようになった。
Commandパターンに人気があるのは、メンテナンスが容易、柔軟性がある、そして拡張性に富むからだ。JSFのEA4では、Commandパターンによってアプリケーションハンドラを完全に駆逐した。上の醜悪なコードの中のひとつひとつのif文が、JSFのアクションになるのだ。たとえば、こんなアクションだ。
[JSFのアクションの実装例]
public class LoginAction extends javax.faces.application.Action {
public String invoke() {
// Code for logging in
if(someCondition)
return "succeeded";
else
return "failed";
}
}
上のアクションでは、invoke()メソッドが文字列"succeeded"または"failed"を返す。そしてJSFのナビゲーションハンドラがこれらの文字列に基づいて、リクエストの転送先を決める。しかもその指定は、コンフィギュレーションファイルの中に簡単にこのように書く:
[コンフィギュレーションファイルの中に書くナビゲーションルール]
<faces-config>
<navigation-rule>
<from-tree-id>/login.jsp</from-tree-id>
<navigation-case>
<from-outcome>succeeded</from-outcome>
<to-tree-id>/welcome.jsp</to-tree-id>
</navigation-case>
<navigation-case>
<from-outcome>failed</from-outcome>
<to-tree-id>/login-error.jsp</to-tree-id>
</navigation-case>
</navigation-rule>
</faces-config>
上のXMLは、/login.jspから来たリクエストに対する、二つのナビゲーション先を指定している。もしも結果がsucceededならば、ナビゲーションハンドラは/welcome.jspをロードする。結果がfailedならば、ナビゲーションハンドラはlogin-error.jspをロードする。
以上のようにして、まずアクションを実装し、その結果をナビゲーションのシステムに結び付ける。では、そのアクションを、どうやって使うのか?。たとえばJSPページの中では、actionRefという属性でアクションを参照する:
<%@ taglib uri='http://java.sun.com/jsf/html_basic' prefix='h' %@>
<h:command_button actionRef='login.action'/>
上のタグは、ボタンをひとつ表現している。ボタンのactionRef属性の値が、login.actionだ。ユーザがそのボタンをクリックすると、loginという名前のビーンのgetAction()メソッドが動きだす。このビーンは、最初に参照されたときにJSFが自動的に作ってくれる。ビーンがコンフィギュレーションファイルに指定されているから、JSFはそのとおりにビーンを作るのだ。
[コンフィギュレーションファイルの中]
<faces-config>
...
<managed-bean>
<managed-bean-name>login</managed-bean-name>
<managed-bean-class>actions.Login</managed-bean-class>
</managed-bean>
...
</faces-config>
上で指定されているLoginクラスは、こんなふうに書かれている:
public class Login {
public Action getAction() {
return new LoginAction();
}
}
Login.getAction()メソッドは、上でコードを見たLoginActionのインスタンスを指す参照を返す。そこで、actionRef='login.action'というJSPタグの指定は、Login.getAction()の呼び出しへと解決(リゾルブ)され、後者がLoginActionのインスタンスの参照を返す。そうするとJSFはLoginAction.invoke()を呼び出し、次のようにログインの仕事を実行する:
public class LoginAction extends javax.faces.application.Action {
public void invoke() {
// ここでログイン処理をするか、または、
// 純粋なビジネスオブジェクトに処理を委託す
}
}
ヴァリューチェンジドイベント
JSFでは、上で述べたように、アクションがビジネスロジックを実装し動かしていく。しかしときには、イベントに対応してユーザインタフェイスのロジックを実装したい場合がある。たとえば図2のアプリケーションを見てみよう。ユーザが自分の国としてアメリカ合衆国を指定すると、州名や都市名を入力するためのFORMが出る。しかしユーザが日本を指定すると、県名や市町村名を入力するためのFORMが必要だ。
以上のようなことが、“ユーザインタフェイスロジック”の一例だ。
そしてユーザインタフェイスロジックは、JSFのヴァリューチェンジドイベントを扱うヴァリューチェンジドリスナで実装する。
図2:変様可能なユーザインタフェイス(上は合衆国の場合)
(画像は原文上に)
図3:変様可能なユーザインタフェイス(上はカナダの場合)
(画像は原文上に)
上のアプリケーションでは、次のような簡単なヴァリューチェンジドリスナを実装すればよい。このコードは、ログインを担当するJSPページの一部だ。
コード5:ヴァリューチェンジドリスナの使い方
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
<html>
<%@ taglib uri='http://java.sun.com/jsf/core' prefix='f' %>
<f:use_faces>
<head>
<%@ taglib uri='http://java.sun.com/jstl/fmt' prefix='fmt' %>
<%@ taglib uri='http://java.sun.com/jsf/html' prefix='h' %>
<%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %>
<fmt:setBundle basename='messages' scope='session' var='messages'/>
<title>
<h:output_text
key='register.window.title' bundle='messages'/>
</title>
</head>
<style type='text/css'>
@import url(<c:out
value='${pageContext.request.contextPath}'/>/site.css);
</style>
<body>
<h:output_text key='register.page.title' bundle='messages'
outputClass='emphasis'/>
<p>
<h:form formName='blank'>
<h:panel_grid columns='2'>
<h:output_text key='register.streetAddress.prompt'
bundle='messages'/>
<h:input_text id='streetAddress'
valueRef='registerForm.streetAddress'/>
<h:output_text id='cityPrompt' key='register.city.prompt' bundle='messages'/>
<h:input_text id='city' valueRef='registerForm.city'/>
<h:output_text id='statePrompt' key='register.state.prompt' bundle='messages'/>
<h:input_text id='state' valueRef='registerForm.state'/>
<h:output_text key='register.country.prompt' bundle='messages'/>
<h:selectone_menu valueRef="registerForm.country"
onchange="submit()">
<f:valuechanged_listener type='listeners.CountryChanged'/>
<h:selectitems
valueRef='registerForm.countryNames'/>
</h:selectone_menu>
</h:panel_grid>
<p>
<h:command_button actionRef='registerForm.action'
key='register.submit' bundle='messages'
commandName='immaterial'/>
</h:form>
</body>
</f:use_faces>
</html>
上のコード5では、都市欄や州欄のためにユニークなIDを割り当てている。
コード6:ヴァリューチェンジドリスナの実装例
package listeners;
import javax.faces.event.ValueChangedEvent;
import javax.faces.event.ValueChangedListener;
import javax.faces.event.PhaseId;
public class CountryChanged implements ValueChangedListener {
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
public void processValueChanged(ValueChangedEvent event) {
UIComponent c = (UIComponent)event.getSource(),
cityPrompt = c.getParent().findComponent("cityPrompt"),
statePrompt = c.getParent().findComponent("statePrompt");
if("Canada".equals(event.getNewValue())) {
cityPrompt.setAttribute ("key", "register.municipality.prompt");
statePrompt.setAttribute("key", "register.province.prompt");
}
else {
cityPrompt.setAttribute ("key", "register.city.prompt");
statePrompt.setAttribute("key", "register.state.prompt");
}
}
}
上のリスナは、まず都市欄や州欄の参照を取得し、そして指定された国名に応じてそれらの値を変えている(上のコード例では日本ではなくカナダである)。
JSFの前途は明るい
JavaServer Faces(JSF)は、最初のEAから大きく成長した。最初のころあった重大な欠陥は、すべて手当てされた。今日では、デベロッパはJSFを使って、堅牢で、メンテナンス性が良く、拡張性に富むWebアプリケーションを書くことができる。もうすぐベータリリースが出る予定だから、データグリッド機能や、使いやすい国際化機能などの実装も期待される。JSFは、Strutsに代わって、JavaによるWebアプリケーションフレームワークのスタンダードになることを狙っている。スタンダードと認められれば、ユーザもIDEベンダもこぞって採用することになるだろう。
--end of text--