レベル: 中級 Ilya Platonov (ill@isg.axmor.com), Software Engineer and Developer, Axmor Artem Papkov (artem@us.ibm.com), Solution Architect, IBM Jim Smith (jamessmi@us.ibm.com), Manager, IBM
2008年 4月 08日 JSF と Struts は、開発者が Web アプリケーションの開発をする際に昔からよく使われているコンポーネント・フレームワークです。しかし、これらに代わる方法があります。Tapestry と Wicket はコンポーネント指向の Web フレームワークであり、Web アプリケーションを作成するために設計されています。ここでは Tapestry と Wicket の技術を使って、ToDo リストのワークフローを実装する簡単なサンプル・アプリケーションを作成します。
Tapestry と Wicket は、新しい、おなじみのコンポーネント・ベースの Web フレームワークとして宣伝されています。Model 2 アーキテクチャーのフレームワークである Struts や Spring などの MVC とは異なり、Tapestry と Wicket は、Web アプリケーションやその動作、そしてコンポーネント間の相互作用について、GUI ベースのスタンドアロン・アプリケーションの場合と同じように考えるための方法に重点を置くことによって、Web 開発のプロセスにまったく新しい手法を提供しています。
典型的なコンポーネント・ベースのアプリケーションでは、その各ページは、コンポーネントのセットで構成され、表されています。そしてそれらのコンポーネントは、さらに小さなコンポーネントで構成されているかもしれず、その小さなコンポーネントは、それよりもさらに小さなコンポーネントで構成されているかもしれません。この場合、ユーザーとの対話動作は特定のコンポーネントのイベントになります。これは MVC ベースのアプリケーションとの大きな違いで、MVC ベースのアプリケーションでは、対話動作のエントリー・ポイントは、一般的な属性 (URL や、サーブレットやアクションのパラメーター、フォームなど) を持ったサーブレットまたはアクションです。
コンポーネント・ベースのフレームワークを使用する開発者は、イベント駆動のモデルを実装することによって、コンポーネントと、そうしたコンポーネントによる対話動作に焦点を当てます。サーブレットや HTTP セッションなどといったサーブレット API のエンティティーは、1 つ下のレベルに移動され、直接使用されることはありません。その一方で、開発者は、コンポーネントまたはコンポーネントのプロパティーに関する宣言方法 (セッションに固有の場合もあります) を通じて、サーバー・サイドの状態を管理します。
JSP や Velocity マークアップを使った JSF や WebWorks とは異なり、Tapestry と Wicket は共に、HTML 標準に完全に準拠した HTML テンプレートを作成できる、独自のテンプレート・システムを使います。これによってコンサーン (関心事) が明確に分離されます。Web デザイナーは、アプリケーションがどんなプラットフォーム上に作成されるのかを気にせずに GUI の作業を行うことができます。同様に、アプリケーション開発者はデバッグやテストにスタブ要素を使うことによって、ページの最終的なデザインを気にせずにコンポーネントを実装することができます。Tapestry と Wicket はどちらも、こうしたコンサーンの分離をサポートしています。
Tapestry の概要
現状では、このプロジェクトから正式にリリースされているバージョンは Tapestry V4.1 です。V5.0 は現在、開発が活発に行われており、この記事の執筆時点で、Tapestry のチームが Tapestry をゼロから書き直し始めてから約 18 カ月が経過しています。V5.0 はまだ正式にリリースされていませんが、後方互換ではないという意味で、それまでのバージョンとはまったく異なると言ってよいと思います。V5.0 はまだ安定していないことを考え、ここでは V4.1 に焦点を絞ることにします。
Tapestry V4.1 がその大部分のベースとしているのは、IoC (Inversion-of-Control) コンテナーを本格的に実現した HiveMind マイクロカーネルです。Tapestry のすべてのサービスは HiveMind を使って登録されます。
Tapestry の典型的なコンポーネントは、XML 記述子であるコンポーネント仕様と、Java™ プログラミング言語の一部であるコンポーネント・ロジック、そして HTML テンプレートである UI レイアウトから構成されます。
Wicket の概要
Wicket のアーキテクチャーの中心には Component クラスがあり、すべてのコンポーネントとマークアップ・コンテナーはこのクラスを継承します。コンポーネントは、そのコンポーネントのモデル (IModel インターフェースの実装) を扱います。モデルは、そのコンポーネントのインスタンスに関係する任意の種類のデータを表し、またそのコンポーネントの動作を定義します。IModel の実装として注目すべきものの 1 つが LoadableDetachableModel です。LoadableDetachableModel では、変化するデータを渡し、そのデータをコンポーネントの描画前にロードし、そのデータが不要になったら分離 (破棄) するといったことができます。そのため Wicket のセッションのサイズを小さくすることができます。
Wicket のページもコンポーネントであり、それらのページがステートフルの場合、ページの状態は Wicket のセッションの中に保存されます。ページはバージョンで管理できるため、(単にページが表示された、ページのコンポーネントの状態が変更された、など) ページの状態が変更されるたびに、インクリメントされたバージョン番号とともにページの状態が保存されます。
開発プロセス
このセクションでは、典型的な開発作業が Tapestry と Wicket でどのように実現されるかを説明します。以下に示すコード・スニペットの大部分は、コンポーネント群やページ群などの、もっと大きなソースから引用したものです。従って、ここでは説明しないビジネス・メソッドの呼び出しを含んでいる場合があります。
データ型のサポート
どちらのフレームワークも Java データ型をサポートしています。Tapestry は OGNL (Object-Graph Navigation Language) を採用しています。OGNL は、Java オブジェクトのプロパティーを取得し、設定するための式言語です。Wicket はデータ・バインディングに独自の機構を使っています。オブジェクトのプロパティーを動的に取得し、更新するための便利な実装として、PropertyModel と CompoundPropertyModel という 2 つがあります。
では float 型の使い方の例を調べてみましょう。Tapestry では、float の値を持つテキスト・フィールドは次のように規定されます。
リスト 1. Tapestry のコードの例
<component id="weight" type="TextField">
<binding name="value" value="ognl:weight"/>
<binding name="translator" value="translator:number,pattern=#.#"/>
<binding name="displayName" value="literal:Weight"/>
</component>
|
Wicket では、そうしたテキスト・フィールドは次のように規定されます。
リスト 2. Wicket のコードの例
FormComponent field = new TextField("weight", Float.class);
field.setLabel(new Model("Weight"));
add(field);
|
従って、string、integer、float などの単純なデータ型、さらには BigDecimal なども自然な方法で使うことができます。
検証
通常、すべてのユーザー入力をビジネス・ルールに対して検証する必要があります。Tapestry と Wicket はどちらも、フォームの入力フィールドに対して、サーバー・サイドまたはクライアント・サイドで (DHTML を使って) 検証を行うことができます。このタスクをサーバー・サイドで行うための方法を考えてみましょう。これらのフレームワークでのフィールド入力の検証は、次のような特定のバリデーターによって行われます。
リスト 3. Tapestry のバリデーター
<component id="weight" type="TextField">
...
<binding name="validators" value="validators:min=1,max=500"/>
</component>
|
リスト 4. Wicket のバリデーター
...
field.add(NumberValidator.range(1, 500));
|
また、既存のバリデーターがどれも要求を満足できない場合には、カスタムのフィールド・バリデーターを定義することもできます。もし何らかのバリデーション・エラーが発生した場合には、その入力が無効であったことをユーザーに対して表示する必要があります。
Tapestry では、デフォルトの ValidationDelegate フォーム Bean によるフィールド追跡に対してループを定義し、フォームに関する既存のすべてのエラーを表示する必要があります。
リスト 5. ValidationDelegate フォーム Bean
<property name="currentFieldTracking"/>
<component id="errors" type="For">
<binding name="source" value="beans.delegate.fieldTracking"/>
<binding name="value" value="currentFieldTracking"/>
</component>
<component id="isInError" type="If">
<binding name="condition" value="currentFieldTracking.inError"/>
</component>
<component id="error" type="Delegator">
<binding name="delegate" value="currentFieldTracking.errorRenderer"/>
</component>
|
これに対応する HTML マークアップを以下に示します。
リスト 6. Wicket のバリデーター
<ul jwcid="errors">
<li jwcid="isInError">
<span jwcid="error">Form validation error </span>
</li>
</ul>
|
Wicket では、複数のエラーを表示する機能は、既に FeedbackPanel: add(new
FeedbackPanel("feedback")); の中に実装されています。HTML マークアップは <span wicket:id="feedback" /></div> です。
クライアント・サイドの検証を有効にするためには、Tapestry の場合には以下のように clientValidationEnabled パラメーターをフォームに追加します。
リスト 7. Tapestry でのクライアント・サイドの検証
<component id="actionForm" type="Form">
<binding name="delegate" value="beans.delegate"/>
<binding name="clientValidationEnabled" value="true"/>
</component>
|
Wicket では、AjaxFormValidatingBehavior クラスがクライアント・サイドの検証に使われます。
リスト 8. Wicket でのクライアント・サイドの検証
AjaxFormValidatingBehavior.addToAllFormComponents(form, "onkeydown");
// Add the button to submit the form using AJAX
form.add(new AjaxButton("ajax-button", form) {
protected void onSubmit(AjaxRequestTarget target, Form form) {
target.addComponent(feedback);
}
protected void onError(AjaxRequestTarget target, Form form) {
target.addComponent(feedback);
}
});
|
制御用の構成体
ある人に割り当てられた作業のリストがあり、それを HTML の表で表示したいとしましょう。Tapestry では、集合に対して繰り返し実行するためには、標準コンポーネントである For を使います (リスト 9、10、11)。
リスト 9. Tapestry でのページ仕様
<component id="receivedItems" type="For">
<binding name="source" value="ognl:receivedItems"/>
<binding name="value" value="ognl:currentItem"/>
</component>
<component id="itemId" type="Insert">
<binding name="value" value="ognl:currentItem.itemId"/>
</component>
<component id="subject" type="Insert">
<binding name="value" value="ognl:currentItem.subject"/>
</component>
<component id="creator" type="Insert">
<binding name="value" value="ognl:currentItem.creator"/>
</component>
<component id="recipient" type="Insert">
<binding name="value" value="ognl:currentItem.recipient"/>
</component>
|
リスト 10. Tapestry での Java クラス
public abstract List getReceivedItems();
public abstract void setReceivedItems(List items);
public abstract ActionItem getCurrentItem();
|
リスト 11. Tapestry での HTML マークアップ
<tr jwcid="receivedItems">
<td><span jwcid="itemId">ID</span></td>
<td><span jwcid="subject">Subject</span></td>
<td><span jwcid="creator">Creator</span></td>
<td><span jwcid="recipient">Recipient</span></td>
</tr>
|
Wicket では ListView クラスを使い、そしてこのクラスの populateItem() メソッドを匿名実装します (リスト 12 と 13)。
リスト 12. Wicket での Java クラス
add(new ListView("receivedItems", items) {
protected void populateItem(final ListItem item) {
ActionItem todo = (ActionItem) item.getModelObject();
item.add(new Label("itemId", String.valueOf(todo.getItemId()));
item.add(new Label("subject", todo.getSubject()));
item.add(new Label("creator", todo.getCreator()));
item.add(new Label("recipient", todo.getRecipient()));
}
});
|
リスト 13. Wicket での HTML マークアップ
<tr wicket:id="receivedItems">
<td><span wicket:id="itemId">ID</span></a></td>
<td><span wicket:id="subject">Subject</span></td>
<td><span wicket:id="creator">Creator</span></td>
<td><span wicket:id="recipient">Recipient</span></td>
</tr>
|
目標をもっと複雑にし、ある条件に従ってテキストを描画する動作を追加しましょう。例えば表の偶数行と奇数行に CSS のクラス名を追加します。そこで次のようなものを追加します。Tapestry でのページ仕様に関してはリスト 14 と 15 を見てください。
リスト 14. Tapestry でのページ仕様
<component id="receivedItems" type="For">
...
<binding name="index" value="ognl:currentIndex"/>
<binding name="class" value="ognl:currentStyleClass"/>
</component>
|
リスト 15. Tapestry での Java クラス
...
public abstract int getCurrentIndex();
public String getCurrentStyleClass() {
return (getCurrentIndex() % 2 == 0) ? "list-row-even" : "list-row-odd";
}
|
リスト 16. Wicket での Java クラス
add(new ListView("receivedItems", items) {
protected void populateItem(final ListItem item) {
...
item.add(new AttributeModifier("class", true, new AbstractReadOnlyModel() {
public Object getObject() {
return (item.getIndex() % 2 == 0) ? "list-row-even" : "list-row-odd";
}
}));
}
});
|
ページネーション
これで、このユーザーのすべての作業がこのページに表示されるようになりました。しかし時間と共にさらに作業が発生してくるため、間もなく、1 つの表に収まらないほどになります。そうなると、複数ページにまたがる表が非常に望ましいものになります。
Tapestry には、便利な Table コンポーネントと、その下位レベルのコンポーネント (Contrib ライブラリー・モジュールの TableView など) が用意されています (リスト 17 と 18)。
リスト 17. Tapestry でのページ仕様
<component id="receivedItemsView" type="contrib:TableView">
<binding name="source" value="ognl:itemsTableModel"/>
<binding name="columns" value="itemId, subject, creator, recipient"/>
<binding name="pageSize" value="10"/>
</component>
<component id="receivedItemsColumns" type="contrib:TableColumns" />
<component id="receivedItemsRows" type="contrib:TableRows" />
<component id="receivedItemsValues" type="contrib:TableValues" />
<component id="receivedItemsPages" type="contrib:TablePages">
<binding name="pagesDisplayed" value="10"/>
</component>
|
リスト 18. Tapestry での Java クラス
public IBasicTableModel getItemsTableModel() {
return new IBasicTableModel() {
public int getRowCount() {
return getActionItemManager().getActionItemsCountByRecipient(uid);
}
public Iterator getCurrentPageRows(int nFirst, int nPageSize,
ITableColumn objSortColumn, boolean bSortOrder) {
return getActionItemManager()
.getActionItemsListByRecipient(uid, nFirst, nPageSize);
}
};
}
|
リスト 19. Tapestry での HTML マークアップ
<span jwcid="table">
<table>
<tr><span jwcid="receivedItemsColumns" class="title"/></tr>
<tr jwcid="receivedItemsRows">
<td jwcid="receivedItemsValues"/>
</tr>
</table>
<span jwcid="receivedItemsPages"/>
</span>
|
Wicket には、以下に示すように、DataView クラスと、IDataProvider の実装が用意されています (下記)。
リスト 20. Wicket の DataView クラス
public class ItemsDataProvider implements IdataProvider {
public Iterator iterator(int first, int count) {
return getActionItemManager().getActionItemsListByRecipient(uid, first, count);
}
public int size() {
return getActionItemManager().getActionItemsCountByRecipient(uid);
}
public IModel model(Object object) {
return new LoadableDetachableModel(object);
}
}
public ListActionItems extends WebPage {
public ListActionItems() {
DataView dataView = new DataView("receivedItemsView", new ItemsDataProvider()) {
protected void populateItem(final Item item) {
ActionItem todo = (ActionItem) item.getModelObject();
item.add(new Label("itemId", String.valueOf(todo.getItemId()));
item.add(new Label("subject", todo.getSubject()));
item.add(new Label("creator", todo.getCreator()));
item.add(new Label("recipient", todo.getRecipient()));
}
};
dataView.setItemsPerPage(10);
add(dataView);
add(new PagingNavigator("receivedItemsPages", dataView));
}
}
|
リスト 21. Wicket の DataView の HTML マークアップ
<table>
<tr>
<th>ID</th>
<th>Subject</th>
<th>Creator</th>
<th>Recipient</th>
</tr>
<tr wicket:id="receivedItemsView">
<td><span wicket:id="itemId">ID</span></td>
<td><span wicket:id="subject">Subject</span></td>
<td><span wicket:id="creator">Creator</span></td>
<td><span wicket:id="creationDate">Creation Date</span></td>
<td><span wicket:id="deadline">Deadline</span></td>
</tr>
</table>
<span wicket:id="receivedItemsPages">Paging navigator links</span>
|
国際化
国際化のサポートは、複数言語のアプリケーションを開発する際の意志決定をする上で、重要な役割を果たします。Tapestry と Wicket には、ほとんど数え切れないほどのローカライズ機能が用意されています。コンポーネント・テキスト (ラベルやメッセージなどの通常のテキスト) や、画像などの静的リソース、さらにはマークアップ・テンプレート全体でさえ、以下に示すように、ローカライズの対象リソースの名前にロケール識別子を持つ接尾辞を追加するだけでローカライズすることができます。
リスト 22. Tapestry と Wicket でのローカライズ
ListActionItems.html
ListActionItems.properties
ListActionItems_ru_RU.html
ListActionItems_ru_RU.properties
|
ローカライズされたメッセージに対してページ仕様またはテンプレートからアクセスする際には、Tapestry コンポーネントのパラメーターに接頭辞 message: を使います。Wicket の場合には、ローカライズに関する作業を行うのに役立つ、強力な StringResourceModel クラスがあります。
ブックマーク、パンくずリスト、そして「戻る」ボタン
Tapestry と Wicket はそれぞれ独自のフォーマットで URL を生成します。これらの URL は、ブックマークを付けられるようなものには見えません。そのため、ページを簡単に参照することができません。しかしこの 2 つのフレームワークには、そうした場合のためのソリューションも用意されています。
Tapestry の場合には、外部から参照されるページは、以下のような IExternalPage インターフェースを実装する必要があります。
リスト 23. Tapestry での IExternalPage の実装
public abstract class ViewActionItem extends BasePage implements IExternalPage {
public abstract Integer getItemId();
public abstract void setItemId(Integer itemId);
public void activateExternalPage(Object[] params, IRequestCycle cycle) {
setItemId((Integer) params[0]);
}
public void pageBeginRender(PageEvent pageEvent) {
ActionItem todo = getActionItemManager().getActionItemByItemId(getItemId());
}
}
|
さらに、このページを http://host/webapp/tapestryapp?page=ViewActionItem&service=external&sp=123 として参照することができます。しかし Tapestry のページに直接アクセスするための方法は、この方法だけではありません。HiveMind のサービス・エンコーダーの 1 つである page-service-encoder が使えるのです。このエンコーダーはページの名前を、URL の中で読めるものにエンコードすることができます。
リスト 24. HiveMind の構成: hivemodule.xml
<module id="com.ibm.cit.tvw" version="1.1.0">
<contribution configuration-id="tapestry.url.ServiceEncoders">
<page-service-encoder id="page" extension="html" service="page"/>
</contribution>
</module>
|
また以下に示すように、.html 拡張子に対するサーブレット・マッピングを web.xml に追加することを忘れないでください。
リスト 25. hivemodule.xml にサーブレット・マッピングを追加する
<servlet-mapping>
<servlet-name>TodolistApplication</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
|
こうしておくと、アプリケーションのすべてのページを、例えば http://host/webapp/ListActionItems.html としてアクセスすることができます。こうなればブックマークを付けることができます。ただしこの方法ではリクエスト・パラメーターを提供できないことに注意してください。ページは引数なしで (あるいはデフォルトの引数を付けて) 呼び出されます。
Wicket の場合には以下のように、PageParameters を引数に使ってページ・コンストラクターを定義すればよいだけです。
リスト 26. Wicket と PageParameters
public class ListActionItems extends WebPage {
public ListActionItems(PageParameters parameters) {
int id = parameters.getInt("id");
// do something
}
}
|
すると、これ以降、このページを http://host/webapp/wicketapp/?wicket:bookmarkablePage=%3Acom.ibm.cit.tvw.wicket.page.ViewActionItem&id=123 として参照することができます。また、以下に示すように、必要なパスにページをマウントすることもできます。
リスト 27. 必要なパスにページをマウントする (Wicket の場合)
public class ToDoListApplication extends WebApplication {
protected void init() {
mountBookmarkablePage("/apath/login.html", Login.class);
}
}
|
この方法によって、ページの URL を扱う際に非常に柔軟な操作を行うことができます。
今度はパンくずリストに注目します。パンくずリストはあるクライアントによるアクションの履歴です。Wicket には、パンくずリストをサポートするためのインターフェースが 2 つあります (IBreadCrumbModel と IBreadCrumbParticipant)。これらのインターフェースのモデルは、クライアントによるアクションを 1 つの有効なパンくずリストの構成要素として表現します。モデル・インターフェースの実装である BreadCrumbBar は、便利なことにパンくずリストの構成要素のスタックを保持することができます。新しい構成要素は、その要素がスタックの中に存在しなければ、スタックの先頭に追加されます。すでにスタックの中に存在する場合は、そこまでのスタックが切り離されることになります。
一方 Tapestry には、パンくずリスト用にそのまま使えるソリューションがありません。
次に、「戻る」ボタンを見てみましょう。Web アプリケーションの開発者達は、Web ブラウザーの「戻る」ボタンを使うことによって生じる予想外の結果を防ぐために、常々このボタンをなくしたいと思っていました。しかし「戻る」ボタンは相変わらず残っています。そのためアプリケーションは、このボタンに関連するすべてのイベントに対して適切に対応しなければなりません。
この点に関しては Wicket が優れています。Wicket はページのバージョン管理を使用しており、ページのバージョンが有効になっていれば、ページの状態は、現在のバージョンの前の任意のバージョンに容易に戻すことができます。ページの状態は VersionManager によって管理され、また状態の変化はユーザーのセッションに保存されます。しかし、この機能を使う際には十分に注意する必要があります。
ではこれに対応するものとして、Tapestry には何が用意されているのでしょう。Tapestry では、「戻る」ボタンのための容易な方法は用意されていません。1 つの選択肢としてクライアント・サイドのパーシスタンスを使う方法があるかもしれませんが、それは簡単ではなく、特別な考慮を必要とします。
カスタム・コンポーネント
コンポーネント・ベースのフレームワークの主要な特徴の中で、最も強力なものの 1 つは、カスタム・コンポーネントの再利用です。これらのフレームワークでは独自のコンポーネントを記述することができ、そうしたコンポーネントはページ全体であってもよく、小さなパネルや、さらには 1 つの HTML 要素であっても構いません。コンポーネント・テンプレートの設計は、当然ながらコンポーネントの継承によって実現することができ、もはや頭を悩ますようなものではありません。そうしたテンプレートを作成しておけば、アプリケーションの任意の場所に、必要なだけ何度でも、それらを簡単に注入することができます。Tapestry と Wicket では、それぞれ独自の方法で、何ら制限されることなくコンポーネントの再利用を管理することができます。
カスタム・コンポーネントは、さまざまなアプリケーションで使用できるようになると、本当に再利用可能なものになります。Wicket では、そのコンポーネントのクラスとマークアップ・テンプレートを含む JAR アーカイブを作成するだけでコンポーネントをパッケージ化することができます。そのアーカイブをアプリケーションのクラスパスに置くと、自動的にそれらのコンポーネントを利用できるようになります。Tapestry では、カスタムのコンポーネント・ライブラリーを定義するためには、もう少しステップが必要であり、JAR アーカイブはライブラリーを指定する要素を持つ XML 記述子を含む必要があります。このライブラリーを使用するアプリケーションでは、そのアプリケーションの構成ファイルの中に、そのライブラリーが指定されている必要があります。
再利用可能なコンポーネントを採用した最高の例の 1 つが、一般的なナビゲーション・パネルやヘッダーとフッター、広告領域などを持つページ・テンプレートです。下記のサンプル・テンプレートは、ホームへのリンクと現在ログインしているユーザーの名前、そして Logout ボタンを表示するコンポーネントです。
Tapestry での Template コンポーネントの仕様をリスト 28、29、30 に示します。
リスト 28. Tapestry での Template コンポーネントの仕様
<component-specification allow-body="yes"
allow-informal-parameters="no"
class="com.ibm.cit.tvw.tapestry.component.Template">
<!--HTML head -->
<asset name="stylesheet" path="/style.css" />
<parameter name="pageTitle" default-value="literal:Todolist Application" />
<component id="shell" type="Shell">
<binding name="stylesheet" value="assets.stylesheet" />
<binding name="title" value="ognl:pageTitle" />
</component>
<!-- HTML body -->
<component id="body" type="Body" />
<!-- The name of the currently logged user -->
<component id="user" type="Insert">
<binding name="value" value="ognl:currentUser"/>
</component>
<!-- The home page -->
<component id="homeLink" type="PageLink">
<binding name="page" value="literal:ListActionItems" />
</component>
<!-- User Profile page -->
<component id="profileLink" type="DirectLink">
<binding name="listener" value="listener:onEditUserProfile"/>
</component>
<!-- Logout link -->
<component id="logoutLink" type="ServiceLink">
<binding name="service"
value="ognl:@org.apache.tapestry.Tapestry@RESTART_SERVICE"/>
</component>
<!-- Page content -->
<component id="pageContent" type="RenderBody"/>
</component-specification>
|
リスト 29. Tapestry での Template コンポーネントの仕様 (Java クラス)
public abstract class Template extends BaseComponent {
public String getCurrentUser() {
return getPage().getRequestCycle().getInfrastructure()
.getRequest().getUserPrincipal();
}
}
|
リスト 30. Tapestry の Template コンポーネントの仕様 (HTML マークアップ)
<html jwcid="shell">
<body jwcid="body">
<div id="top">
<div id="header">
<a jwcid="homeLink" class="homelink">Action Items Sample Application</a>
Current user: <span jwcid="user">Davy Jones</span>
<a jwcid="logoutLink">Logout</a></span>
</div>
<div id="page-content">
<span jwcid="pageContent">This is a page content.</span>
</div>
</div>
</body>
</html>
|
他のページはリスト 31 に示すテンプレートを使っています (これは ViewActionItem ページの例です)。
リスト 31. ViewActionItem の例
<page-specification>
<component id="template" type="Template">
<binding name="pageTitle" value="literal:View Action Item"/>
</component>
...
</page-specification>
|
リスト 32. HTML マークアップによる ViewActionItem
<span jwcid="template">
<!-- Page content -->
...
</span>
|
Wicket では、テンプレートはベース・ページとして表現されます (リスト 33 と 34)。
リスト 33. Wicket のテンプレート
public abstract class TemplatePage extends WebPage {
private String title;
public TemplatePage() {
add(new Label("title", new PropertyModel(this, "title")));
add(new BookmarkablePageLink("homeLink", getApplication().getHomePage()));
add(new Label("user", ((WebRequest) getRequest()).getHttpServletRequest()
.getUserPrincipal().getName()));
add(new Link("logoutLink") {
public void onClick() {
getSession().invalidate();
getRequestCycle().setResponsePage(getHomePage());
getRequestCycle().setRedirect(true);
}
});
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
|
リスト 34. HTML マークアップで表現した Wicket のテンプレート
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:wicket="http://wicket.apache.org/">
<head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="stylesheet" type="text/css" href="style.css" />
<title wicket:id="title">Sample Page Title</title>
</head>
<body>
<div id="top">
<div id="header">
<a wicket:id="homeLink" class="homelink">Action Items Sample Application</a>
Current user: <span wicket:id="user">Davy Jones</span>
<input type="button" class="button" wicket:id="logoutLink" value="Logout" /></span>
</div>
<div id="page-content">
<wicket:child />
</div>
</div>
</body>
</html>
|
このテンプレートを使う子ページは、Template ページから継承する必要があります (リスト 35 と 36)。
リスト 35. Template ページから継承する
public class ViewActionItem extends TemplatePage {
public ViewActionItem() {
setTitle("View Action Item");
...
}
}
|
リスト 36. Template ページから HTML マークアップとして継承する
<html xmlns:wicket="http://wicket.apache.org/">
<head></head>
<body>
<wicket:extend>
<!-- Page content -->
...
</wicket:extend>
</body>
</html>
|
Ajax のサポート
さて、この 2 つのフレームワークの比較の中で最も興味深い部分にやって来ました。つまり Ajax (Asynchronous JavaScript + XML) の使い方を比較します。
皆さんは、自分が作成した Ajax をサポートした最初の Web アプリケーションと、それを実装するためにいかに苦労したかを覚えているでしょうか。そうです。複数のブラウザーをサポートするためのさまざまな手法を使って、JavaScript で通信を行い、データ解析コードを作成しなければなりませんでした。クライアント・サイドの実装 (常に最悪の実装のように思えます) に比べれば、サーブレットなど些細なものに思えてしまいます。
しかし今では Tapestry や Wicket のようなフレームワークを使うことによって、ほんの数行のコードと数分間の注意深い作業だけで Ajax アクションをアプリケーションに追加することができます。これは驚くほど簡単です。下記の例は単純なシナリオを表しています。ToDo リストの 1 つの項目をクリックすると、対応するデータを持つプレビュー・ペインが (Ajax モードで) 更新されるはずです。
Tapestry では、あるリンクに対する async パラメーターを「真」にセットし、(必要があれば) そのリンクによって更新する必要のあるコンポーネントのリストを定義します (リスト 37、38、39)。
リスト 37. Tapestry でのページ仕様のフラグメント
<component id="previewReceivedItem" type="DirectLink">
<binding name="listener" value="listener:onPreviewItem"/>
<binding name="parameters" value="ognl:currentItem.itemId"/>
<binding name="async" value="true"/>
<binding name="updateComponents" value="ognl:{'preview'}"/>
</component>
<component id="preview" type="Any"/>
<component id="previewSubject" type="Insert">
<binding name="value" value="ognl:previewSubject"/>
<binding name="renderTag" value="true"/>
</component>
<component id="previewState" type="Insert">
<binding name="value" value="ognl:previewState"/>
<binding name="renderTag" value="true"/>
</component>
|
リスト 38. Tapestry でのページ仕様のフラグメント (Java クラス)
public abstract class ListPreviewActionItems extends BasePage {
public abstract String getPreviewSubject();
public abstract void setPreviewSubject(String previewSubject);
public abstract String getPreviewState();
public abstract void setPreviewState(String PreviewState);
public void onPreviewItem(Integer itemId) {
if (itemId != null) {
ActionItem pitem = getActionItemManager().getActionItemByItemId(itemId);
setPreviewSubject(pitem.getSubject());
setPreviewState(pitem.getCurrentState());
}
}
}
|
リスト 39. Tapestry でのページ仕様のフラグメント (HTML マークアップ)
<h3>Preview Action Item</h3>
<div jwcid="preview">
Subject: <span id="previewSubject" jwcid="previewSubject">subject</span><br/>
State: <span id="previewState" jwcid="previewState">current state</span>
</div>
|
Wicket の場合には、onClick() メソッドが実装された AjaxLink を使うだけです (リスト 40 と 41)。
リスト 40. Wicket の AjaxLink
public class ListPreviewActionItems extends WebPage {
public ListPreviewActionItems() {
final LoadableActionItemModel previewItem = new LoadableActionItemModel(null);
WebMarkupContainer preview = new WebMarkupContainer("preview");
preview.setOutputMarkupPlaceholderTag(true);
preview.add(new Label("previewSubject", new PropertyModel(previewItem, "subject")));
preview.add(new Label("previewState", new PropertyModel(previewItem, "currentState")));
add(preview);
AjaxLink previewReceivedItem = new AjaxLink("previewReceivedItem") {
public void onClick(AjaxRequestTarget target) {
previewItem.setItemId(currentItem.getItemId());
target.addComponent(previewContainer);
}
};
add(previewReceivedItem);
}
}
|
リスト 41. Wicket の AjaxLink の HTML マークアップ
<h3>Preview Action Item</h3>
<div wicket:id="preview">
Subject: <span wicket:id="previewSubject">subject</span><br/>
State: <span wicket:id="previewState">current state</span>
</div>
|
これで終わりです。通信やデータ処理、その他すべての面倒な作業が、背後にあるフレームワークによって実行されるのです。
開発ツール
Tapestry と Wicket というフレームワークの間には、開発プロセスに関して特に注目すべき大きな違いはありません。Tapestry のコンポーネントの実装は、Web アプリケーションの開発をサポートする大部分の Java IDE を使えば容易に行うことができます。Wicket に必要なものはもっと少なく、任意の Java IDE と HTML エディターがあれば開発には十分です。
ただし Java とマークアップでのコンポーネントのモデル・ツリーでの同期の問題に突き当たるかもしれません。しかしその場合には、そのコンポーネントを呼び出すたびに、同期の問題があることが通知されます。これは、そうした問題をコンパイル時に処理する Google Web Toolkit など他のフレームワークとは対照的です。
IBM WebSphere Application Server CE の使い方
Tapestry と Wicket は、Java で作成された Web アプリケーションに対応したフレームワークであるため、通常の場合と同じように Web アプリケーションのビルドやパッケージングを行うことができます。そのアプリケーションを IBM® WebSphere® Application Server Community Edition にデプロイするためには、組み込みの、あるいは別のアプリケーション・デプロイメント・プランを提供しますが、これも通常と同じです。
まとめ
Tapestry と Wicket は、よく使用されるコンポーネント・ベースの Web フレームワークの代表格であり、おそらくそうしたフレームワークの中でひときわ優れたものです。この 2 つのフレームワークを利用することで、どんなに複雑なアプリケーションでも作成することが可能になります。もちろん、静的な情報を持つページは CMS を使って管理した方が適切ですが、ユーザーとアプリケーションとの間で行われる対話動作の数々を処理する際には、フレームワークの利点が発揮されます。
2 つのフレームワークのアーキテクチャーは似ています (どちらもコンポーネント指向であり、コンサーン (関心事) が明確に分離されています) が、両者の実装は非常に異なっています。Tapestry はコンポーネントを指定するのに宣言型の方法を使いますが、Wicket はピュア Java によるプログラムを使用し、XML もアノテーションも使いません。Wicket には、ページのバージョン管理やマルチ・ウィンドウのサポートの面で、いくつか強力な利点があります。また Wicket には、さらにいくつか便利な実装があります (パンくずリストやフィードバック・パネル、また DHTML に見られる興味深い数々のもの (タブ付きのパネルやダイアログなど) など)。
XML を使いたくない場合や、オブジェクト指向プログラミングを好んで使う場合は、日常的に使用するものとして Wicket の方が適切な選択肢です。拡張機能のライブラリーが豊富なことも Wicket の有利な点です。
最後に、Tapestry は現在、移行期にあります。Tapestry V5 は V4.1 よりも多くの面で機能強化されていますが、後方互換性については何も計画されていません。従って、V4.1 で作成されたアプリケーションはそのまま有用なものとして使えるかもしれませんが、現在の Tapestry を使って得られる専門知識は、今後長期にわたって役に立つことはないかもしれません。
参考文献 学ぶために
製品や技術を入手するために
- 皆さんの次期オープンソース開発プロジェクトを IBM trial software を使って革新してください。ダウンロード、あるいは DVD で入手することができます。
-
IBM 製品の試用版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品をお試しください。
議論するために
著者について  | |  | Ilya Platonov は、ロシア・ノボシビルスクにある IBM Advanced Technology Solutions (ATS) Lab の顧問を務める Axmor Software のソフトウェア・エンジニアです。ノボシビルスク州立大学でコンピューター・サイエンスの修士号を取得しました。最近は、5 つを越える ATS プロジェクトにシステム・アーキテクトあるいはソフトウェア・エンジニアとして参加しています。 |
 | |  | Artem Papkov は現在、IBM の Client Innovation Team のソリューション・アーキテクトであり、顧客やビジネス・パートナーが SOA や Web サービスなどの新興技術の採用を進めるための協力を行っています。彼は 1998 年に Belarusian State University of Informatics and Radioelectronics にてコンピューター・サイエンスの修士号で卒業後、2000 年にノースキャロライナ州 Research Triangle Park にある IBM に入社しました。これまでに経験した業務には、新興技術を使用したマルチティア・ソリューションのソフトウェア開発、アーキテクチャー設計、インターネット・ベースのソリューション統合などがあります。彼はこの 3 年間、顧客と密接に協力しながら、彼らが IBM による戦略的統合技術としての Web サービスや統合手法としての SOA の採用を進められるよう業務を行っています。 |
 | |  | Jim Smith は、ソフトウェア開発の分野で 18 年以上の経験を持ちます。彼の経歴は、カリフォルニア州リバーモアの Sandia National Labs での、無数の既存のレガシー・コードを使用した高速データ収集システムおよび分散コンピューティング・システムの設計に始まります。Java 言語とカスタマー向き技術に豊富な経験を持つ彼は、IBM カスタマーのための Java ソリューションの実現に的を絞った Emerging Internet Technologies チームに移りました。彼は、ATS (Advanced Technology Solutions) の設立者の 1 人です。これは、IBM、開発研究所、ビジネス・パートナー、およびカスタマーのための先進技術と軽量ビジネス・プロセスを開発、洗練、および特権付与し、標準技術と IBM 製品の導入とデプロイメントをさらに高速化する使命を持つ世界的なソフトウェア・サービスおよび開発組織です。現在 Jim は、この組織を管理しています。 |
記事の評価
|