Tapestryを使おう

Tapestryとは

一言で言えば、Tapestryは、Webデザイナとプログラマの作業を完全に分離し、両者が平行して作業を進めることを可能にするプレゼンテーション用のフレームワークです。

JavaでWebアプリケーションを開発する上で、プレゼンテーション(デザイン)とロジック(プログラムコード)の分離は大きな課題の一つです。JSPやタグライブラリなど、この課題に取り組んだものが既にいくつかあります。 しかしその多くが、Webデザイナかプログラマのどちらかに、大きな負担を強いるものでした。

Tapestryは、両者がお互いの持分をまもりつつ、協力して作業することを可能にします。もっと具体的に言うと、Webデザイナが作業の対象とするファイルと、プログラマが作業の対象とするファイルは異なります。そして、Webデザイナが作業対象とするのは、ほぼ完全な(X)HTMLファイルです。タグライブラリのような見慣れないタグや、JSPのスクリプトのような難解な文字列は存在しません。JSPやタグライブラリと異なり、実際にアプリケーションサーバやサーブレットエンジンに配備しなくても、そのHTMLファイルをブラウザで開くだけで、デザインを確認することができます(動的に生成される部分も「それなりに」表示されるので、かなりの精度で確認できます)。 プログラマの作業も大したことはありません。通常のJavaのソースと、簡単なXMLの設定ファイルを書くだけです。

Apple社のWebObjectsという製品をご存知の方は、この後を読み進むにつれ、TapestryとWebObjectsが同じアイデアに基いていると感じられるでしょう。私もそう思います。

Tapestryを使うメリット
Tapestryを使う主なメリットは、Webデザイナとプログラマの作業を分離できるという特徴から派生します。
  1. 画面のデザインと、ロジックの実装を平行して行える
  2. 画面のデザインの変更の際、プログラマの手助けが必要ない
Webの利用者が飽きないよう、Webアプリケーション完成後にサイトのデザインを変更するのは当然ですし、開発途中でも依頼主からデザインの変更要求があることを考えると、2番目のメリットは、非常に重要です。
Tapestryを使ったアプリケーションの構成

Tapestryで言うところのアプリケーション(以降、Webアプリケーションと区別するときはTapestryアプリケーションと呼ぶ)は、Webアプリケーションの一部として動作します。そして、一つのWebアプリケーションは、複数のTapestryアプリケーションを含むことができます。

Tapestryアプリケーションで最も中心的な役割を果たすのは、Tapestryアプリケーション全体をコントロールしているApplicationServlet(Tapestryアプリケーション開発者は、これを継承したものを利用する)です。このApplicationServletに、XMLで書かれたTapestryアプリケーションの仕様を表す設定ファイルのパスを渡せば、設定ファイルに従ってTapestryがアプリケーションを制御してくれます。

Tapestryアプリケーションの残りの部分は、ページやページ内の動的コンテンツ部分を表すコンポーネントなどです。なお、Tapestryアプリケーションは、必ずHomeと名づけられたページを持っていなければなりません。このページは、Tapestryアプリケーションに最初に訪れたときに表示されるページです。

Tapestryアプリケーションの構成
図
WebApplication
  TapestryApplication1
    ApplicationServlet 
    アプリケーション仕様
      ページコンポーネントHome
        ページ仕様
	JavaBeans
      ページコンポーネント1
      :
  TapestryApplication2
  :
Tapestryを使う準備
それでは実際にTapestryを使ってみましょう。ここでは、Webコンテナ(Servletエンジン or アプリケーションサーバ)としてTomcat4.0.3を利用した例を示します。Tapestryは、Servletをベースとしていますので、これ以外のWebコンテナでも、簡単にセットアップできるでしょう。J2SEのバージョンは、1.4以上を利用します。 まず次のものをダウンロードします。
J2SE 1.4
http://java.sun.com/j2se/もしくは雑誌のCDなどからJ2SE1.4を入手します。
Jakarta Tomcat 4.0.3
http://jakarta.apache.org/tomcat/ からjakarta-tomcat-4.0.3-LE-jdk14.tar.gzもしくはjakarta-tomcat-4.0.3-LE-jdk14.zipをダウンロードします。
Tapestry 2.0.0
http://sourceforge.net/projects/tapestry/ からTapestry-2.0.0.tar.gzをダウンロードします。
次にこれらをインストールします。J2SEとTomcatのインストールには言及しませんので、適宜他のドキュメントを参照してインストールしてください。ここでは、/opt(UNIXの場合)または、C:\opt(Windowsの場合)の下にインストールされているものとします。 Tapestry-2.0.0.tar.gzは、tar+GnuZipの形式になっていますので、適当な解凍ツールで/opt(UNIXの場合)または、C:\opt(Windowsの場合)の下に展開してください。展開すると/opt/Tapestry-2.0.0またはC:\opt\Tapestry-2.0.0というディレクトリができます。これで準備はおしまいです。Tapestryに付属するデモやチュートリアルを動かすには、JBossなどもインストールしTapestryのセットアップをしなければならないのですが、ここでは自分たちの使おうとしているWebコンテナでTapestryを利用することに焦点を当てますので割愛します(暇があったら書きます:-))。デモの動かし方は、Tapestryに付属のReadme.htmlを参照してください。

以降、Tomcatがインストールされているディレクトリを$TOMCAT、Tapestryがインストールされているディレクトリを$TAPESTRYと表します(例: $TOMCAT/bin/startup.sh, $TAPESTRY/lib/ext)。

最初のTapestryアプリケーション(demo1)
では、簡単なTapestryアプリケーションを作って動かしてみましょう。
  1. Webアプリ用のディレクトリの準備
    これから作るWebアプリケーションのために、下記のディレクトリを作成します。
    $TOMCAT/webapps/demostryWebアプリケーションのルート
    $TOMCAT/webapps/demostry/WEB-INF
    $TOMCAT/webapps/demostry/WEB-INF/libライブラリ置き場
    $TOMCAT/webapps/demostry/WEB-INF/classesクラスファイルやHTMLファイルなどの置き場
    $TOMCAT/webapps/demostry/WEB-INF/srcソースの置き場
    $TOMCAT/webapps/demostry/WEB-INF/src/demostrydemostryパッケージのソースの置き場
  2. ライブラリのコピー
    さて、Tapestryを使ったWebアプリケーションを作るわけですから、Tapestryのクラスライブラリが必要です。 Tapestry自身のライブラリ $TAPESTRY/lib/com.primix.tapestry-2.0.0.jar と、Tapestryが使っているロギングライブラリ $TAPESTRY/lib/log4j-core.jar を $TOMCAT/webapps/demostry/WEB-INF/lib にコピーします。以下の作業でコンパイルする際は、この二つのライブラリと$TOMCAT/common/lib/servlet.jarをクラスパスに指定するのを忘れないようにしてください。
  3. Servletの記述

    TapestryはServletとして実装されていますので、まずServletを実装します。といってもTapestryのユーザは、ApplicationServletというクラスを拡張して、getApplicationSpecificationPath()というメソッドを実装するだけです。このメソッドは、Tapestryのアプリケーション仕様が書かれたファイルのパス返します。このメソッドの戻り値は、java.lang.Class.getResourceAsStream()メソッドの引数として利用されますので、パスは、単なるファイルのパスではなく、クラスローダがアクセスする際のパスになります。 一般的には、WEB-INF/classesからの(先頭に'/'がついた)相対パスになります。ここでは、パスをServlet自身に埋め込まず、web.xmlで値を変更できるようにServletを実装します。以下のファイルを作成し、コンパイルします。出来上がったクラスファイルは $TOMCAT/webapps/demostry/WEB-INF/classes の下に出力されるように-dオプションを指定してください。

    その他のservice()や、doGet()などのメソッドは実装しません。これらは、スーパークラスで適切に実装されていますので、スーパークラスの実装に任せます。

    $TOMCAT/webapps/demostry/WEB-INF/src/demostry/BootstrapServlet.java
    /*
     * Copyright (C) 2002 by Kohji Nakamura
     */
    
    package demostry;
    
    import com.primix.tapestry.ApplicationServlet;
    
    /**
     *  @version $Id$
     *  @author Kohji Nakamura
     */ 
    
    public class BootstrapServlet extends ApplicationServlet {
    	protected String getApplicationSpecificationPath() {
    		String appDef = getServletConfig().
    			getInitParameter("application-def"); // web.xmlで指定されたパラメータを取得
    		return appDef;
    	}
    }
  4. web.xmlの記述
    次に前述のBootstrapServletを使って、正しくTapestryアプリケーションが呼ばれるようにweb.xmlを記述します。ここでは、 http://localhost:8080/demostry/demo1/** というURLにアクセスするとBootstrapServletが呼び出されるように設定します。また、BootstrapServletのgetApplicationSpecificationPath()メソッドが返す値となる"application-def"パラメータを、"/demo1/Demo.application"に設定しています。このTapestryアプリケーション仕様が書かれたファイルの実際の置き場所は、 $TOMCAT/webapps/demostry/WEB-INF/classes/demo1/Demo.application になります。
    $TOMCAT/webapps/demostry/WEB-INF/web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!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>
      <display-name>Demo of Tapestry</display-name>
      
      <servlet>
        <servlet-name>Demo1</servlet-name>
        <servlet-class>demostry.BootstrapServlet</servlet-class>
        <init-param>
          <param-name>application-def</param-name>
          <param-value>/demo1/Demo.application</param-value>
        </init-param>
      </servlet>
    
      <servlet-mapping>
        <servlet-name>Demo1</servlet-name>
        <url-pattern>/demo1/**</url-pattern>
      </servlet-mapping>
    
      <welcome-file-list>
        <welcome-file>index.html</welcome-file>
      </welcome-file-list>
    </web-app>
  5. Check point
    ここまでの作業ができているか確認してみましょう。$TOMCAT/webapps/demostry以下が、次のような構成になっていれば問題ありません(もちろん中身があっていればですが)。
    $TOMCAT/webapps/demostry以下の構成
    demostry/WEB-INF/web.xml
    demostry/WEB-INF/src/demostry/BootstrapServlet.java
    demostry/WEB-INF/classes/demostry/BootstrapServlet.class
    demostry/WEB-INF/lib/com.primix.tapestry-2.0.0.jar
    demostry/WEB-INF/lib/log4j-core.jar
    ここまでは、ライブラリの配置とサーブレットの記述、web.xmlの記述という、どのTapestryアプリケーションにも共通の作業です。この後、Tapestryアプリケーション固有の(Tapestryアプリケーション自身の)開発作業に移ります。
  6. アプリケーション仕様の記述

    それでは、先ほどweb.xmlでパスを指定したDemo.applicationを記述しましょう。このファイルにはTapestryのアプリケーションの仕様を記述します。具体的には、どのようなページから構成されているか、Tapestryアプリケーションが(リクエスト内ではなく)セッションで利用するデータは何か等を記述します。まず手始めに、今日の日付を表示する1つのページだけから成るTapestryアプリケーションを作ってみましょう。 画面は次のようになります。

    アプリケーションの画面イメージ
    Tapestryの世界へようこそ!
    このサーバの時計では、今日は2002/04/16です.

    アプリケーション仕様は次のように記述します。

    $TOMCAT/webapps/demostry/WEB-INF/classes/demo1/Demo.application
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE application PUBLIC "-//Howard Ship//Tapestry Specification 1.1//EN"
    	"http://tapestry.sf.net/dtd/Tapestry_1_1.dtd">
    <application name="Demo 1" engine-class="com.primix.tapestry.engine.SimpleEngine">
      <page name="Home" specification-path="/demo1/Home.jwc"/>
    </application>

    まず、applicationタグの説明です。name属性で好きな名前を付けます。engine-class属性では、Tapestryアプリケーションがユーザからのリクエストを受け取ったときに、リクエストを処理するエンジンクラスを指定するのですが、多くの場合この例に書いてあるSimpleEngineで問題ありません。私もまだ勉強不足でこのあたりはまだよくわかりません(~\(^^)/~)。

    次にpageタグです。このタグは、名前のとおりTapestryアプリケーションで使用するページ(HTMLファイルに相当)を記述します。ここでは、Homeと言う名前を付けています。Homeと言うページ名は、Tapestryでは特別な意味を持ち、ユーザが始めてTapestryアプリケーションにアクセスしたときに表示されるページを表します。Tapestryアプリケーションは、必ず一つのHomeと名づけられたページを持っていなければなりません。

    ページの詳細な仕様は、specification-path属性で指定したファイル(/demo1/Home.jwc)に記述します。このパスもファイルパスではなくJavaのリソースパスなので、通常WEB-INF/classes以下に置きます。ちなみに、拡張子.jwcは、Java Web Componentの略です。

  7. ページ仕様とHTMLテンプレートの記述

    この作業が、Tapestryの核心に迫るところです。最初にHTMLテンプレートを記述しましょう。HTMLテンプレートのファイル名は、先程、アプリケーション仕様でpageタグのspecification-path属性で指定した名前("/demo1/Home.jwc")の拡張子を.htmlに変更したものです。従って、/demo1/Home.htmlと言う名前になります。しかし、Tapestryの地域化(L10N)機能を使って日本語のページを用意するのであれば、ファイルのベース名に"_ja"をつけて、/demo1/Home_ja.htmlとします。英語で書いた、/demo1/Home_en.htmlや/demo1/Home.htmlを用意しておけば、とりあえず日本語と英語に対応できます。このあたりはJavaのリソースバンドルと同じですね。HTMLテンプレートの中身は次のようになります。

    $TOMCAT/webapps/demostry/WEB-INF/classes/demo1/Home_ja.html
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
    <html>
    <head>
    	<title>Hello World</title>
    </head>
    <body>
    
    Tapestryの世界へようこそ!<br />
    このサーバの時計では、今日は<b><span jwcid="date">2002/01/01</span></b>です.
    
    </body>
    </html>

    ほとんどただのHTMLファイルです。唯一特殊なところは、spanタグの jwcid="date" という属性です(spanタグ自体はHTML標準のタグです)。このHTMLの仕様にはないjwcidという属性は、その名のとおりJWC(Java Web Component)のIDを表します。TapestryはこのIDを頼りに、Javaで書かれたロジックと、それに対応するHTMLテンプレートの範囲を対応付けます。次のページ仕様を見ると、その様子がわかると思います。

    $TOMCAT/webapps/demostry/WEB-INF/classes/demo1/Home.jwc
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE specification PUBLIC "-//Howard Ship//Tapestry Specification 1.1//EN"
    	"http://tapestry.sf.net/dtd/Tapestry_1_1.dtd">
    <specification class="demo1.Home">
      <component id="date" type="Insert">
        <binding name="value" property-path="today"/>
      </component>
    </specification>

    specificationタグのclass属性により、Homeというページを担当するJavaのクラス(JavaBeanでなければなりません)はdemo1.Homeであることを指示しています。また、このページにはdateと名づけられたコンポーネント(componentタグのid属性)があり、そのコンポーネントのタイプはInsertであることを指示しています。このInsertというタイプのコンポーネントは、valueパラメータの値で、対応するjwcid(ここではdate)を持ったHTMLテンプレート中のタグ(およびその中身)を置き換えます(詳しくはTapestryのDeveloper's Guideを参照してください)。 valueパラメータの値の指定は、bindingタグで行います。name属性でvalueというパラメータ名を指定します。property-path属性で、ページを担当するクラスからのプロパティパスを指定します。プロパティパスとはBeanのプロパティを"."で連結したもので、"prop1.prop2"というプロパティパスに値を設定する場合、aBeanInstance.getProp1().setProp2(value)となり、値を取得する場合、aBeanInstance.getProp1().getProp2()やaBeanInstance.getProp1().isProp2()となります。 Home.jwcの記述では、demo1.HomeクラスのgetToday()というgetterが返す値が、Home_ja.htmlのdateというJWCIDを持つタグの部分に挿入されます。

  8. ロジックの記述

    最後に、今日の日付を求めるロジックの実装をしましょう。クラスの名前は、Home.jwcのspecificationタグのclass属性で指定した、demo1.Homeになります。このクラスは、ページを表示を司るものなので(Demo.applicationのpageタグでHome.jwcを指定しているので)、com.primix.tapestry.BasePageを継承して実装します。また、todayというプロパティパスで日付を取得することになっているので、getToday()というgetterを実装します。

    $TOMCAT/webapps/demostry/WEB-INF/src/demo1/Home.java
    package demo1;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import com.primix.tapestry.BasePage;
    
    public class Home extends BasePage {
    	public String getToday() {
    		SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
    		return format.format(new Date());
    	}
    }

    このファイルをコンパイルして、classファイルをWEB-INF/classesの下に置けば完成です。

  9. 実行
    以上で準備は整いました。以下のようにファイルが揃っていることを確認したら、tomcatを起動してアクセスしてみましょう。 URLは、http://localhost:8080/demostry/demo1/ になります(ホスト名やポート番号は自分の環境に合わせてください)。ちゃんと結果が表示されたでしょうか?
    $TOMCAT/webapps/demostry以下の構成
    demostry/WEB-INF/web.xml
    demostry/WEB-INF/classes/demo1/Demo.application
    demostry/WEB-INF/classes/demo1/Home.class
    demostry/WEB-INF/classes/demo1/Home.jwc
    demostry/WEB-INF/classes/demo1/Home_ja.html
    demostry/WEB-INF/classes/demostry/BootstrapServlet.class
    demostry/WEB-INF/lib/com.primix.tapestry-2.0.0.jar
    demostry/WEB-INF/lib/log4j-core.jar
    demostry/WEB-INF/src/demo1/Home.java
    demostry/WEB-INF/src/demostry/BootstrapServlet.java
  10. おさらい

    いろいろ説明しながらの作業なので面倒に感じたかもしれませんが、二度三度と繰り返すうちに苦にならなくなると思います。逆に、どこにどんな情報が記述されているか一目瞭然で、再利用しやすい形になっているので、効率がよいと感じるようになるでしょう。

    また、Webデザイナが扱うのは、Home_ja.htmlだけです。プログラマとWebデザイナ間で「jwcid属性の値がdateの部分はその日の日付に置き換わる」と言う取り決めがされていれば、Webデザイナは簡単にこのhtmlファイルを作り上げるでしょう。多くのタグや属性を覚える必要は無く、idと機能を知っていればいいのです。しかも出来上がったファイルは、プログラムと結合しなくても表示イメージを確認できるのです!

    逆に、クライアントとWebデザイナのやり取りで画面イメージが先にできている場合、プログラマはhtmlファイルにjwcid属性(あと必要に応じてspanタグなども)を追加すれば、そのhtmlファイルをそのまま利用できます。

アクションと変化(demo2)

最初のTapestryアプリケーションは、今日の日付を表示するだけの非常に初歩的な動的ページでした。次は、ユーザのアクションに従ってページの内容を変える、もう少し本格的な動的ページを作ってみましょう。

今回のTapestryアプリケーションでは、ユーザがリンクをクリックする度に、お勧めサイト集の中からサイトを一つ選びそのサイトへのリンクを表示するページを作ってみます。画面イメージは次のようになります。

アプリケーションの画面イメージ

今回のお勧めサイトはリンクの名前です.

次のリンク

「リンクの名前」の部分はサイトの名称に置き換わり、そのサイトへのリンクになります。「次のリンク」をクリックすると、「リンクの名前」の部分が次のサイトの名称に変わり、リンクも変わります。

今回は、まずHTMLテンプレートとページ仕様から作ってみましょう。次のようになります。siteNameというJWCIDが付いているところは、前回の日付を挿入する仕組みと同じですね。Insertというコンポーネントを使っています。新しく覚えなければならないのは、JWCIDがurlとrefreshになっている部分です。

$TOMCAT/webapps/demostry/WEB-INF/classes/demo2/Home_ja.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
	<title>Random Link</title>
</head>
<body>

<p>今回のお勧めサイトは<a jwcid="url" href="dummy" style="text-decoration: none"><span jwcid="siteName">リンクの名前</span></a>です.</p>

<p><a jwcid="refresh" href="dummy" style="text-decoration: none">次のリンク</a></p>

</body>
</html>
$TOMCAT/webapps/demostry/WEB-INF/classes/demo2/Home.jwc
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE specification PUBLIC "-//Howard Ship//Tapestry Specification 1.1//EN"
	"http://tapestry.sf.net/dtd/Tapestry_1_1.dtd">
<specification class="demo2.Home">
  <component id="url" type="Any">
    <static-binding name="element">a</static-binding>
    <binding name="href" property-path="url"/>
  </component>
  <component id="siteName" type="Insert">
    <binding name="value" property-path="siteName"/>
  </component>
  <component id="refresh" type="Direct">
    <binding name="listener" property-path="listeners.nextSite"/>
    <field-binding name="stateful" field-name="Boolean.FALSE"/>
  </component>
</specification>

まずJWCIDがurlの方から見ていきます。ページ仕様を見ると、このコンポーネントはAnyというコンポーネントであることが分かります。このAnyというコンポーネントは任意のタグをHTMLテンプレートに挿入するためのものです。 Anyコンポーネントはelementという名のパラメータを一つ持っています。elementパラメータには、どのタグを挿入するかを指定します。<img>タグを挿入したければimgを、<pre>タグを挿入したければpreを値として指定します。ここではアンカータグを挿入したいのでaを指定しています。値の設定にbindingではなく、static-bindingタグを使用しています。bindingタグはプロパティの値を取得して動的に値を割り当てるのに対し、static-bindingタグは、仕様ファイルに書かれた値を静的に割り当てます。

もう一つhrefという名のパラメータに値をバインドしています。実はコンポーネントのパラメータには2種類あって、コンポーネントで明確にパラメータ名とその意味および必須か省略可能かが決められているもの(Anyコンポーネントのelementパラメータに相当)をフォーマルパラメータ、コンポーネントで特に決まった名前を決めていないもの(Anyコンポーネントのhrefパラメータに相当)をインフォーマルパラメータと呼びます。Anyコンポーネントは、elementというフォーマルパラメータの値をタグの名前として扱い、インフォーマルパラメータのパラメータ名をタグの属性名に、インフォーマルパラメータの値を属性値として挿入する仕様になっています。インフォーマルパラメータのおかげで、Anyコンポーネントは様々なHTMLのタグの数多くの属性をうまく扱えるようになっているのです。 インフォーマルパラメータhrefにバインドする値には、プロパティパスでurlを指定してます。実際はdemo2.Home.getUrl()メソッドが返す値が使用されます。

HTMLテンプレートのJWCIDがurlの部分をよく見ると、aタグで書かれています。このタグはAnyのelementパラメータで指定したタグに置き換えられるので、spanでも何でもいいのですがHTMLテンプレートをブラウザで表示させたときと、実際に動作させたときの見た目を一致させるためにaタグを使用しています。同様にhref="dummy"という記述も、Anyコンポーネントのhrefインフォーマルパラメータで置き換わるので、HTMLテンプレートの見た目の正確さを向上させる以上の意味はありません。ただし、style="..."はAnyコンポーネントで特に指定していないので、Anyコンポーネントは何も変更せずそのまま、style属性を出力します。うまくできているでしょ。

次にJWCIDがrefreshの方を見てみましょう。ページ仕様を見ると、このコンポーネントはDirectになっています。Directコンポーネントは、HTML上はリンクを挿入するという動作をします。しかし、もっと便利な機能も持っています。挿入されたリンクをクリックすると、特定のメソッドを呼び出してくれるのです。どのメソッドが呼び出されるかは、Directコンポーネントのlistenerパラメータで指定します。property-path属性でlisteners.nextSiteが指定されています。通常はプロパティパスの起点のクラス(ここではdemo2.Home)のインスタンスに対して、getListeners().getNextSite()というメソッドが呼び出されて、取得されたリスナのメソッドが呼び出されるところですが、プログラマは、このような複雑な動作を気にしないで済むようになっています。「listeners.メソッド名」とプロパティパスに書くと、起点のクラス(ここではdemo2.Home)の「メソッド名」という名のメソッドを呼び出してくれます。実際は、(間接の)親クラスのcom.primix.tapestry.AbstractComponent等で、Javaのリフレクション機能などを使って動的にリスナを作成し、結果として単に起点のクラスのメソッドが呼び出されるようになっているのです。 難しい話しは置いといて、「listeners.メソッド名」とプロパティパスに指定すればよいと覚えておきましょう。ここの記述では、demo2.HomeクラスのnextSiteメソッドを呼び出すような設定になっています。 HTMLテンプレートでは、JWCIDがurlの時と同様、refreshでもaタグやhref="dummy"と記述していますが、こう書いてある理由もurlの時と同様、HTMLテンプレートをブラウザで見た時の表示イメージの正確さを向上させているだけです。

次にロジックの実装を見てみましょう。ページ仕様で、プロパティパスurlと、siteNameを指定しました。また、Directのlistenerパラメータで、プロパティパスlisteners.nextSiteを指定しましたので、getUrl(), getSiteName(), nextSite()メソッドを実装しなければなりません。nextSiteはvoid型で、String[]型と、IRequestCycleの引数をとります。引数の詳細については次の例題で説明します。ソースができたらコンパイルしておきましょう。

$TOMCAT/webapps/demostry/WEB-INF/src/demo2/Home.java
package demo2;

import java.util.Map;
import com.primix.tapestry.BasePage;
import com.primix.tapestry.IRequestCycle;

public class Home extends BasePage {
	private Object[] siteList;
	private int index = 0;
	private Map.Entry site;

	public Home() {
		Map sites = new java.util.HashMap();
		sites.put("SlashDot", "http://www.slashdot.org/");
		sites.put("SourceForge", "http://sf.net/");
		sites.put("LinuxOnline", "http://www.linux.org/");
		siteList = (Object[])sites.entrySet().toArray();
		next();
	}
	/**
	 * getUrl, getSiteNameが返す値を切り替える
	 */
	private void next() {
		site = (Map.Entry)siteList[index++];
		index %= siteList.length;
	}
	public String getSiteName() {
		return (String)site.getKey();
	}
	public String getUrl() {
		return (String)site.getValue();
	}
	/*
	 * nextSiteが呼ばれるメカニズムに興味がある人は下記のクラス等を
	 * 参照
	 * @see com.primix.tapestry.AbstractComponent.getListeners(),
	 * com.primix.tapestry.listener.ListenerMap
	 */
	public void nextSite(String[] context, IRequestCycle cycle)
	{
		next();
		//cycle.setPage("Home");
	}
}

最後に、アプリケーション仕様の作成とweb.xmlの変更を行って、仕上げましょう。

$TOMCAT/webapps/demostry/WEB-INF/classes/demo2/Demo.application
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application PUBLIC "-//Howard Ship//Tapestry Specification 1.1//EN" "http://tapestry.sf.net/dtd/Tapestry_1_1.dtd">
<application name="Demo 2" engine-class="com.primix.tapestry.engine.SimpleEngine">
  <page name="Home" specification-path="/demo2/Home.jwc"/>
</application>
$TOMCAT/webapps/demostry/WEB-INF/web.xml(強調部分が変更分)
<?xml version="1.0" encoding="UTF-8"?>
<!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>
  <display-name>Demo of Tapestry</display-name>
  
  <servlet>
    <servlet-name>Demo1</servlet-name>
    <servlet-class>demostry.BootstrapServlet</servlet-class>
    <init-param>
      <param-name>application-def</param-name>
      <param-value>/demo1/Demo.application</param-value>
    </init-param>
  </servlet>

  <servlet>
    <servlet-name>Demo2</servlet-name>
    <servlet-class>demostry.BootstrapServlet</servlet-class>
    <init-param>
      <param-name>application-def</param-name>
      <param-value>/demo2/Demo.application</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>Demo1</servlet-name>
    <url-pattern>/demo1/*</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>Demo2</servlet-name>
    <url-pattern>/demo2/*</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
</web-app>

以下に、ファイルの一覧を示します。強調してあるファイルは、追加/変更したファイルです。確認が済んだら、Tomcatをリスタートして http://localhost:8080/demostry/demo2/ にアクセスしてみてください。意図どおりの動作をするでしょうか?

$TOMCAT/webapps/demostry以下の構成
demostry/WEB-INF/web.xml
demostry/WEB-INF/classes/demo1/Demo.application
demostry/WEB-INF/classes/demo1/Home.class
demostry/WEB-INF/classes/demo1/Home.jwc
demostry/WEB-INF/classes/demo1/Home_ja.html
demostry/WEB-INF/classes/demo2/Demo.application
demostry/WEB-INF/classes/demo2/Home.class
demostry/WEB-INF/classes/demo2/Home.jwc
demostry/WEB-INF/classes/demo2/Home_ja.html
demostry/WEB-INF/classes/demostry/BootstrapServlet.class
demostry/WEB-INF/lib/com.primix.tapestry-2.0.0.jar
demostry/WEB-INF/lib/log4j-core.jar
demostry/WEB-INF/src/demo1/Home.java
demostry/WEB-INF/src/demo2/Home.java
demostry/WEB-INF/src/demostry/BootstrapServlet.java

では、ブラウザを2つ開いて、交互にブラウザを使い分けながら、出来上がったアプリケーション(http://localhost:8080/demostry/demo2)の「次のリンク」をクリックしてみてください。一つのブラウザだけ使った場合と、お勧めサイトの出現順が異なると思います。このdemo2の例では、アプリケーションの状態(次のどのお勧めサイトを表示するか)がセッションごとに独立に管理されておらず、ユーザ間で共有されていることを示しています。Tapestryは、明示的に指定しない限り、このようにセッションごとに情報を持ちません。少ないリソースで効率よくサーバが動作できるように、こういう仕様になっています。 もちろん、ショッピングサイトなどでは、ユーザごとに別々に情報を管理しなければなりません。しかし、検索サイトなど、サーバ側でユーザごとに情報を持たなくても提供できるサービスも多いのです。アクセスの多いサイトほど、Cookieやパラメータを使って、できるだけ(サーバ側ではなく)クライアント(ブラウザ)側に情報をもたせるように工夫されています。

複数のページと、データの共有(demo3)【執筆途中】

ここまで、すべてHomeというただ一つのページからなるアプリケーションばかり作ってきました。そろそろ複数のページからなるアプリケーションを作ってみましょう。また、セッションごとに個別にデータを持つ方法も紹介します。テーマとしては、先程ちょっと触れた検索サイトを、あえてサーバ側でクライアントごとの情報を持つ方式で作ってみましょう。具体的には、サーバ側でセッションのデータとして検索条件や検索結果、何件目を表示しているかっといった情報を持つようにします。






spindle

ソースファイルなどをjarにまとめたものをここに置きますので、展開して実行してみてください。
許可なく転載を禁じます。
Copyright (C) Kohji Nakamura 2002.
All rights reserved.