Strutsサンプルアプリケーションを散策する

この文書の目的は、Strutsに付属するサンプルアプリケー ションを「ウォークスルー」し、初めてStrutsを使おうとしているみなさんに Strutsを紹介するということです。Strutsに関する他のドキュメントについて はStrutsユーザーズガイドと Struts APIをご参照ください。

この文書は、Struts 1.0ビルドの動作に基づいています。 また、この文書の読者はすでにStrutsの開発用コピー、およびサンプルアプリ ケーションを(StrutsのREADMEに従い)インストールしており、自分の開発用ワー クステーション(例えばlocalhost)にこのサンプルアプリケーションを置いて あれこれと探索してみる準備ができていることを想定しています。

また、この文書は読者がJava言語、JavaBeans、Webアプ リケーション、そしてJavaServer Pageについての基本を理解していることを 想定しています。これらのトピックスに関する背景的な知識は、それぞれの Sun Javasoft製品のサイトを参照ください。



標準のStrutsパッケージはstruts-test、 struts-documentation、struts-template、そしてstruts-exampleという4つの アプリケーションとともに配布されています。struts-exampleについてREADME 文書は以下のように説明しています:

「このサンプルアプリケーションは、ポータルアプリケー ションの初歩的なものであり、利用者は自分を登録し、Internetのどこかにあ るメールサーバに対する自分の購読情報を管理することができます。このアプ リケーションが完成した暁(あかつき)には、このアプリケーションを通じて多 種のメールサーバからメールを読むことができるようになるでしょう。」

このサンプルはまだ不完全なものですが、Strutsを紹介 するのにとても役立つものです。Strutsとサンプルアプリケーションのインス トールに関するより詳しい情報についてはstrutsのREADMEファイルを参照くだ さい。

index.jsp

インストールが完了すると、標準的なウェルカムページであるindex.jspから 入ってサンプルアプリケーションを利用することができます。このページは2 つのリンクを表示します。ひとつは登録用のリンクであり、もうひとつは(登 録が済んだら利用できる)ログイン用のリンクです。

このような画面表示の背後で、index.jspはデータベー スサーブレットとメッセージリソースが存在するかどうかをチェックします。 これらのオブジェクトは両方ともアプリケーションの設定ファイルweb.xmlで 参照されており、 index.jsp が表示される前にロードされていなければなり ません。もしこれらがなんらかの理由で利用できないなら、index.jspはエラー メッセージを表示します。

このエラーメッセージはウェルカムページ中にハードコー ディングされていることにご注意下さい。こうなっているので、メッセージリ ソースの場所が不明でもエラーメッセージを表示することができるのです。他 のページ中では、メッセージを検索したり表示したりするために、利用者のロ ケール情報に従ったメッセージリソースが用いられます。

web.xml and ApplicationResources.properties

アプリケーションのweb.xmlをチェックすることで一連 のオブジェクトがどのようにロードされるのかがわかります。メッセージリソー スはActionServletに対するアプリケーションパラメータに応じてロードされ ます。ActionServletの初期化時に、ActionServletはパッケージフォルダの ApplicationResources.propertiesをパース処理してデフォルトメッセージリ ソース中に組み込みます。リソース中のメッセージを変更した場合には、アプ リケーションを再ロードすることでそれがアプリケーションの動作に反映され るようになります。

コンテナを再スタートせずにコンフィギュレーションと メッセージリソースを再ロードすることも可能です。詳細についてはweb.xml ファイルの末尾部分を参照ください。

DatabaseServlet.java

データベースオブジェクトは自分自身の初期化ブロック を持っています。データベースサーブレットはデータベースの内容をXMLファ イルとして保存します。このXMLファイルはStruts digesterによってパース処 理され、入れ子になったハッシュテーブルの集まりとして読み込まれます。外 部テーブルはユーザオブジェクトのリストであり、それぞれのユーザオブジェ クトは自分の購読情報に関する、入れ子になったハッシュテーブルを持ってい ます。登録すると、ユーザオブジェクトはこのハッシュテーブル中に格納され、 ログインするときにユーザオブジェクトがセッションコンテキスト中に格納さ れます。

これ以降、説明について来ようとするならば(そうすべ きですよ!)、jakarta-strutsフォルダの下にあるsrc/exampleフォルダの配下 にあるパッケージのソースコードを併せて参照ください。

このサンプルアプリケーションでは、データベースに最 初からサンプルユーザが設定されています。database.xmlファイルを見てみる とサンプルユーザが以下のように記述されているのがわかります:

<user username="user" password="pass" fullName="John Q. User" fromAddress="John.User@somewhere.com">
<subscription autoConnect="false" host="mail.hotmail.com" type="pop3" username="user1234" password="bar" />
</user>

この記述は、hotmailから購読するための詳細情報を伴 う「John Q. User」氏の登録レコードを生成します。

データベースサーブレットのソースコードを見てみると、 サーブレットの属性名がパッケージの定数ファイルから読み込まれていること に気付くでしょう。この方法は名前などの文字列値がソースファイルの間をま たがって同一であることを保証するためのうまいやり方です。

index.jsp 2

index.jspの後の方に、Strutsカスタムタグのうまい使 用例がいくつかあります。ここで着目すべきなのはbaseタグとlinkタグの2つ です。baseタグは、このページに含まれる他の相対的なハイパーリンクを正し く動作させるため、現ページのURLを返します。linkタグにはもう1つの重要な サービスが与えられています。重要なサービスとは、ハイパーリンクを手早く 書けるようにするということに加え、クライアントがクッキーとしてセッショ ンを格納できない場合にクライアントのセッションを維持するためにハイパー リンクをエンコードする、ということです。

ブラウザの設定でクッキーの使用をオフにしてこのペー ジをリロードすれば、セッションid情報が付加されたリンクを見ることができ るでしょう。(Internet Explorerを使っている場合に試すには、適切なセキュ リティゾーンのクッキーをリセットした上で「セッションごと」のクッキーを 不許可にすることを確認してください。)

この単純なウェルカムページにおいて、Strutsは結構多くの ことをすでにやっています:

index.jspページの冒頭部分に、タグライブラリをロー ドするいくつかのディレクティブがあることに気づいたでしょうか。これらは どんなJavaソースファイルにも付随しているおまじないにすぎないものです。

logon.jsp

さて次に、ログオンのリンクを選択した場合にはコンテ ナはlogon.jspファイルをロードします。ここでは、デフォルトのユーザ名と パスワード(user:pass)でログインすることができます(ユーザ名とパスワード は両方とも大文字・小文字の違いを区別することに注意)。さらに、入力しな かったりスペルを間違えた場合にどうなるかとか、いろいろ組み合わせてアプ リケーションの反応を観察してみてください。

このようなことをすると、Strutsは同じJSPに戻ってき ますが、以下のような3つの違いがあります:

  1. ページのアドレスはlogin.jspではなく今やlogon.doである。
  2. Strutsは、ログオンフォームの上に、バリデーションエ ラー(Validation Error)を表示する。
  3. 前に入力したユーザ名が何であれ、それがこのフォーム のデフォルト値として使われる。

かっこいいでしょう。さて、これはどうやって動くのでしょう?

struts-config.xmlLogonForm.java

まず最初に、logon.jsp は「form」カスタムタグを利用 します。このタグは(ウェルカムページからリンクされている)パス 「/logon.jsp」と関係付けられたフォームbeanのために、アプリケーションプ ロパティをスキャンすることができます。この場合、Strutsは1つを見つけ、 この特定のフォームbeanのインスタンスをチェックします。1つも見つからな かった場合、Strutsは新しいフォームbeanを生成します。フォームがサブミッ トされた時、StrutsはHTTPリクエスト中のフォームのフィールド情報を処理し、 待機中のbeanをアップデートします。

これらすべては、単に以下のよう にすることで利用できます:

  1. フォームbeanのためのクラス(settterとgetterメソッドつきのフォームのフィー ルド)をパッケージ中に定義します。
  2. アプリケーションのコンフィギュレーションリソースに、 このbeanクラスを加え
  3. アクションマッピング中で、名称とその beanクラスを結びつけます(name="logonForm")。

Strutsのフォームタグには、HTMLの標準的なオプション パラメータに加え、JavaScriptの機能を追加してくれるいくつかの便利なパラ メータを指定することができます。例えば、フォーカスに関する機能や onsubmitとonresetなどの機能などです。またカスケーディング・スタイルシー トを指定するパラメータもあります。

Strutsはバリデーションつきのフォームとエラーメッセー ジ表示のためのきちんとしたメカニズムを持っています。アクションオブジェ クトはStrutsの標準コレクションに対し、必要ならいくらでもメッセージを付 加することができます。また、JSPはたった一つのカスタムタグ <html:errors/>タグを使うことにより、すべてのメッセージを表示し、キュー をクリアすることができます。あなたのバリデーションルーチンがPOSTの面倒 をみるのに、必要ならいくらでも多くのメッセージを使用できます。

Strutsは、このメカニズムをエラーメッセージハンドラ と呼んでいますが、あなたのアプリケーションでは、エラーだけではなく他の 意味のメッセージに使用することもできます。たとえば、レコードが追加され たり削除された、というようなメッセージをポストするとかです。

フォームbeanを最大限に活用するために、Strutsは特別なクラスActionForm を提供します。ActionFormは、バリデーションとメッセージ処理のためのビル トインサポートがついており、自分用のフォームbeanのためのベースクラスと して利用することができます。あなたが作るJSPフォームはそれぞれ固有のフィー ルドの集合を持っているでしょうから、それらは専用のフォームbeanを持つこ とになるでしょう。

LogonAction.java

最初のJSPはフォームの情報をlogon.doに対してサブミッ トします。サンプルのweb.xml中のサーブレットマッピングを見ると、*.doへ のリクエストがStrutsの「action」サーブレット(ActionServletのインスタン ス)に送られるのが分かります。この例では、ActionServletは(他にもありま すがとりわけ)それ自身のマッピングのためにstruts-config.xmlを参照します。 そこはlogin.doへの参照がある場所です:

<!-- Process a user logon -->
<action
path="/logon"
type="org.apache.struts.webapp.example.LogonAction"
name="logonForm"
scope="request"
input="/logon.jsp"
>
</action>

そして「loginForm」アクションで実行されるべきフォー ムbeanは以下のように指定されます:

<!-- Logon form bean -->
<form-bean
name="logonForm"
type="org.apache.struts.webapp.example.LogonForm"
/>

このアクションマッピングでは、pathプロパティは ActionServletに対して、logon.doへのリクエストをLogonActionオブジェクト に転送せよと指示しています。inputプロパティはLogonAction オブジェクト に対して、ユーザからの情報を得るにはどこに制御を渡せばよいかを指示して います。

LogonActionにリクエストを渡す前に、ActionServletは LogonForm beanを探します。もし見つけたなら、ActionServletはHTTPリクエ スト中の名前が付いたプロパティを、フォームbeanでの名前にマッチさせ、 beanをアップデートします。もしbeanが見つからないなら、ActionServletは それを生成するので、LogonActionはそれがすでに存在すると想定することが できます。

ActionServletによって呼び出される時、LogonAction はLogonForm beanからユーザ名とパスワードを取り出します。(もし作られた ばかりのものであれば、 beanはデフォルト値を返すでしょう)

このサンプルではLogonActionは次に、ログオン情報が 登録されたユーザと一致するかどうか見るために、DatabaseServletに問い合 わせを行います。もしログオン情報がマッチしないのであれば、 LogonAction はエラーリストにメッセージのキーを加えます。ルーチンの最後でエラーリス トが空でないなら、 LogonAction はセッションコンテキストにユーザ beanを 加え、その入力フォーム(login.jsp)に制御を転送します。

DatabaseServletに対する直接的なアクセスは、 LogonActionによってではなく、ビジネスロジックbeanによって処理されるべ きです。サンプルの著者の言葉を借りれば、「このサンプル中のこれはバグと みなされるべきだ」とのことです。

もしエラーがないなら、LogonActionは(なんらかの先行 するユーザ beanで置き換えて)ユーザ beanをセッションコンテキスト 中に置いて、制御を「success」アクションに転送します。その制御が実際に どこに行くかはstruts-config.xml中のマッピングによって決定されます。

成功したログインから戻る前に、LogonActionは LogonForm beanを廃棄します。このため、もしユーザが後でindex.jsp フォー ムに戻ったときに表示されるのは、すでに入力された(古い)ログイン情報がな い、きれいなフォームであるでしょう。LogonActionは、スコープが「リクエ スト」にセットされたかどうか見るために最初に調べ、そして次に beanをリ クエストコンテキストから、あるいはデフォルトセッションコンテキストから 取り除くことに注意してください。

Strutsのベストプラクティスは、関連するすべてのプロ パティを含んでいる単一ページのフォームのためにリクエストスコープを使う、 というものです。なぜならリクエストをまたがってこのようなフォームbean を持続する必要がありませんから。

サンプルではスコープにかかわらずLogonForm beanを 削除することに注意してください。これは以前のコンフィグレーションとの後 方互換性のためです。あなたのアプリケーションでは、あなたはコンフィグレー ションのアドバイスに従って、スコープが「リクエスト」にセットされる場合 に限り、それを取り去るべきです。 このふるまいは、struts-config.xmlを編 集し、アプリケーションを再ロードするだけで変えることができます。

さて、こんどはデフォルトのユーザ名とパスワード (userとpass)を使って、ログインを成功させてみてください。

struts-config.xml 2

すでに述べたように、ログインが成功した場合には、 LogonActionは制御を「success」アクションにフォワードします。このとき制 御が実際に行く所はstruts-config.xml中のマッピングによって決定されます。 LogonActionのマッピングをチェックすれば、以下のような部分を見つけるこ とができます。

<!-- Process a user logon -->
<action
path="/logon"
type="com.husted.struts.example2.LogonAction"
name="logonForm"
scope="request"
input="/logon.jsp">
</action>

おや?! 成功した場合のマッピングはどこにあるのでしょ う? この周りを探してみると、以下のようになっています

<!-- Global Forward Definitions -->
<global-forwards>
<forward
name="logon"
path="/logon.jsp"
/>
<forward
name="success"
path="/mainMenu.jsp"
/>
</global-forwards>

ここで言っているのは、誰かが「success」へフォワー ドせよと指示した時、「success」に対するローカルフォワードが無ければパ ス「/mainMenu.jsp」が使われる、ということです。(同様に 「logon」へのフォワードは「/logon.jsp」を使います)

こういうことなので、あなたは以下の選択肢を表示して いるmainMenu.jspの結果を注意深く観察してください。

ブラウザで表示されたページのパスをチェックすると、 それが「mainMenu.jsp」ではなく「logon.do」を表示しているのがわかるでしょ う。これはページがlogon.doリクエストの最終的な結果としてロードされたか らであり、ブラウザは、そこにあなたがいると思っています。このためにbase カスタムタグは重要なのです。もしあなたのページがイメージに対する相対的 なリンクを含むのであれば、あなたのブラウザは「logon.do」へのパスをベー スとするようにするでしょう。なので、Strutsのbaseタグは、ブラウザが リクエストしたファイルに対してではなく、Strutsが返したファイル に対して相対リンクを解決せよと指示することによって、これを可能とし ます。

もしあなたの目が鋭いなら、logon.doにはログインフォー ムから引き継いだ(「logon.do?username=user」みたいな)パラメータが続いて いないことにもお気付きかもしれません。Strutsフォームタグのためのデフォ ルトのメソッドはPOSTなので、GETメソッドでの場合とは異なりフォームパラ メータは追加されません。これはHTMLのフォームタグの振る舞いとは逆ですね。 HTMLのフォームタグではGETがデフォルトですから。

mainMenu.jsp

mainMenu.jspのソースコードをチェックすると面白い新 タグをいくつか見つけることができるでしょう。最初は「app:checkLogon」タ グです。これは標準的なStrutsのカスタムタグではなく、サンプルアプリケー ションのために設計されたものです。ファイルの最初にある指示子は、アプリ ケーションのタグがapp.tld で定義されていると言っています。app.tldの内 容を追いかけていくと、このタグのソースコードが(意外なことに!) CheckLogonTagであることがわかります。

CheckLoginTag.java

これはアプリケーションロジックをカプセル化するため にカスタムタグを使うことについての良い例になっています。 CheckLoginTag.javaはセッションコンテキストで「User」という名前のオブジェ クトを調べることによってユーザがログインしているかどうかを判断している ようです。ユーザがログインしていなければ制御は「/login.jsp」に転送され ます。ページがアクセスされる前に、アクセスしてきた人がログインしている ことを確認したいときは、JSPの先頭に「<app:checkLogon/>」とだけ置い てください。

CheckLoginTagのソースを良く見ると、コードの保守性 を高めるための、簡単で素早いやりかたが分かります。(たぶん)

ヒント: 「一貫性が重要」

気付きにくいのですが、mainMenuページの見出しは現在 のユーザに応じてカスタマイズされています。たとえば私がログインを新しく 行って、mainMenuページに戻ったときには「Main Menu Options for user」が 表示される代わりに「Main Menu Options for thusted」[訳注: thustedは原 著者 Ted HustedのUser ID]が表示されるでしょう。このようにmainMenu.jsp は通常のjsp:beanタグとstrutsカスタムタグとを並べて使う方法を示していま す。(混同する心配はありません!) ユーザbeanからあなたのユーザ名を取り出 し、HTMLの見出しでそれを表示するためには、単に通常どおり「jsp:useBean」 や「jsp:getProperty」タグを使えばよいだけです。

不幸にも、アプリケーションのモデルの幾分かをこのペー ジのビューが外部にさらしてしまいます。Strutsはこの種のことを最小にする ために努力しているのですが、不可避である場合もあります。

我々が見てきた他のリンクでは、JSPファイルに直接、 あるいはStrutsアクションパスに対してlogin.doのように指定していました。 この「Edit your user registration profile」のリンクは少し違っています。 なぜならそれは「editRegistration.do?action=Edit」のようにパラメータを 使っているからです。このリンクを処理する時、StrutsのActionServletはパ ラメータをリクエストのマッチング時には無視しますが、アクションオブジェ クトへはちゃんと渡してくれます。

このことは、Strutsにおいてアクションオブジェクトが そのベースパスに対するすべての有効なパラメータを処理することができなけ ればならないことを意味します。(このサンプルでは、 editRegistrationは EditとCreateの両方を扱わなくてはなりません)

また、あなたは無効なパラメータをチェックしたいかも しれません。(大文字小文字の違いを区別して比較したいのなら、その相違に ついてはご注意を!)

struts-config.xmlをチェックすると、 editRegistrationアクションが(またまた意外にも!)EditRegistrationAction に対応付けられていることがわかるでしょう。これはregistrationForm bean を使用し、registration.jspから入力を受けます。

<!-- Registration form bean -->
<form-bean name="registrationForm"
type="org.apache.struts.webapp.example.RegistrationForm"/>

<!-- Edit user registration -->
<action path="/editRegistration"
type="org.apache.struts.webapp.example.EditRegistrationAction"
name="registrationForm"
scope="request"
validate="false"
input="/registration.jsp">
<forward name="success" path="/registration.jsp"/>
</action>

ヒント: このサンプルを通じて使われているような一貫 したネーミングルールは、アプリケーションの記述や理解を容易にしてくれま す。この種のことがらについては、創造力の方は抑えておき、 Elements of Java Styleのような確立されたソースコード形式に関する規範に従うのが よいでしょう。

EditRegistrationAction.java

アプリケーションにおいては、多くのオブジェクトが二 つの役割を果たすかもしれません。例えば、EditRegistrationAction は登録 情報を更新するだけではなく、新しい登録情報を作るためにも使われます。オ ブジェクトがどちらの処理を行うかは渡されたアクションによって決定されま す。EditRegistrationActionの場合は「編集」か登録の「作成」のどちらかで す。処理が指定されていない場合、デフォルトでは「作成」を行います。 「?create」と「?edit」のいずれかをハイパーリンクあるいはformアクション に追加することによって処理を選択することができます。

このサンプルアプリケーションにおけるほとんどのクラ スがそうであるように、editRegistrationは処理を追跡するため、ログをうま く使用しています。ActionServletにはサンプルアプリケーションが書かれた 後で追加された新しいログメソッドがあることにお気をつけください。 今となっては、あなたはメッセージを指定した上で最小のログレベル(あるい はdebugレベル)を指定することができます。この点に関してより詳細な情報に ついては、struts-documentationアプリケーション中のJavadocを参照くださ い。

registration.jsp and RegistrationForm.java

mainMenuから「Edit your user registration profile」 というリンクをたどると、ついに、このサンプルアプリケーションの心臓部で ある登録ページに到達します。このページでは、サンプルアプリケーションが あなたについて(あるいは少なくともあなたのログインについて) 知っている すべての情報を表示します。同時に、興味深いテクニックのいくつかをデモン ストレーションしてくれます。

mainMenu.jspでは、ユーザがログインしていることを確 認しなければなりませんでしたが、そのためにCheckLoginタグを使ったことを 思い出してください。registration.jspは少し異なっています。こちらでは、 最初に新しいユーザを登録しようとしているかどうかを調べるためにStrutsの logicタグを使用します。そうでなければ(例えば action != "Create")、 logicタグは我々が編集対象のユーザを持っている(したがって登録情報を持っ ている)ことを確認するためにCheckLoginTagを表示します。

<logic:equal
name="registrationForm"
property="action"
scope="request"
value="Edit"
>
<app:checkLogon/>
</logic:equal>

Strutsの「html:form」タグはstruts-config.xmlによっ て設定されるプロパティを参照し、存在しなければ自動的にregistrationForm beanを作るであろうことに注意してください。とはいえこの処理は、formタグ がページ中で処理されるまでは実行されません。このブロックは「html:form」 タグの前に現われるので、(editRegistration.doアクションを通してではなく) 直接registration.jsp にアクセスしようとするとランタイムエラーが発生し ます。

registation.jspは、ひとつのJSPが複数の仕事を行うこ とができるように、ページを通じてlogicタグを使い続けます。 例えば、もし あなたがフォームを編集しているなら(action == "Edit")、registrationForm beanからあなたのユーザ名を挿入します。もしあなたが新規ユーザなら (action == "Create")、ユーザ名を入力できるように空フィールドを作ります。

Strutsのlogicタグは、ページ中でアプリケーションの ロジックを記述するためのとても便利な方法です。logicタグを使用すること で、ユーザエラーの発生を防ぎ、維持管理しなければならないJSPの個数を減 らす、というようなことをはじめとし、様々な利益を得ることができます。

このページでは、任意のユーザの購読情報のリストを表 示するためにlogicタグを使っています。もしユーザがリクエストコンテキストで editアクションでこのページに入ってきたなら、購読情報をリストアップして いるページの下の方は以下のlogicタグによって表示されます:

<logic:equal
name="registrationForm"
property="action"
scope="request"
value="Edit"
>

そうでなければ、このページの一番上の部分にはユーザ 登録を作るための空欄のデータ・エントリーフォームだけが含まれます。

logic:iterate

通常の条件テストと並列して、他のアクションへ制御を フォワードするためのロジックタグを使用することができます。あるいは、コ レクションに対して繰り返し処理を行うことができます。登録ページは、ユー ザの購読情報を表示するための、「logic:iterate」タグの良い使用例を含ん でいます。

購読情報はハッシュテーブルオブジェクト中に格納され ており、それはユーザオブジェクトに順番に格納されています。ですので、そ れぞれの購読情報を表示するためには、ユーザオブジェクトをまず取り出し、 それに対して購読情報のメンバのループ処理を行わなければなりません。 iterateタグを使ったからといって、この処理がもっと簡単になるというわけ ではありません。

<logic:iterate name="user" property="subscriptions" id="subscription">
<!-- block to repeat -->
</logic:iterate>

iterateタグに対する3つのパラメータ(name, propertyそしてid)は 以下を意味しています

  1. 「user」という名前の属性(例えばオブジェクト)がないかこのコンテキストをチェックせよ。
  2. 「subscriptions」という名前のユーザプロパティを取得せよ。
  3. 反復するブロック中では「subscription」を それぞれのコレクションのメンバーのため名前として使用せよ。

また、HTMLの順序なしリストを使って、それぞれの購読に使用されるホス ト名をリストアップするには以下のように書くことができます:

<ul>
<logic:iterate name="user" property="subscriptions" id="subscription">
<li>
<bean:write name="subscription" property="host" filter="true" />
</li>
</logic:iterate>
</ul>

このコードは、Strutsと標準JSPタグとの協調動作につ いてのもう一つの良い例です。ここで指定されているfilterオプションは、 HTML変換コマンドを文字エンティティに対して使用せよ、ということを示して います。なので<はHTML中では&lt;として出力されます。

registration.jspにおいては、iterateは購読のメニュー を生成するのに使われます。そのメニューの項目はそれぞれ編集と削除アクショ ンにリンクされています。

<logic:iterate id="subscription" name="user" property="subscriptions">
<tr>
<td align="left">
<bean:write name="subscription" property="host" filter="true"/>
</td>
<td align="left">
<bean:write name="subscription" property="username" filter="true"/>
</td>
<td align="center">
<bean:write name="subscription" property="type" filter="true"/>
</td>
<td align="center">
<bean:write name="subscription" property="autoConnect"/>
</td>
<td align="center">
<app:linkSubscription page="/editSubscription.do?action=Delete">
<bean:message key="registration.deleteSubscription"/>
</app:linkSubscription>
<app:linkSubscription page="/editSubscription.do?action=Edit">
<bean:message key="registration.editSubscription"/>
</app:linkSubscription>
</td>
</tr>
</logic:iterate>

iterateタグ中で使用できるコレクションは以下のうち のいずれかです: オブジェクトの配列、Iterator、Collection(List,Set,Vectorを含む)ある いはMap(Hashtablesを含む)。そして、それらの要素は最後まで反復されます。

それぞれの購読情報に対するeditおよびdeleteアクショ ンへのハイパーリンクがカスタムタグ「app:linkSubscription」によって記述 されることに注意ください。アクションに対してハイパーリンクを書いてしま うことは難しくありませんが、それだと醜くなる可能性があり、カプセル化の 観点からは「エクセレントな」一例を作ってしまうことになります。app.tld を経由してソースをたどっていくと、linkSubscriptionタグのソースコードの 定義が、(さてどこにあるでしょうか、当ててごらん!) LinkSubscriptionTag.javaにあることがわかるでしょう。

LinkSubscriptionTag.java

サンプルアプリケーションでは購読対象のホスト名(例 えば「yahoo.com」)をプライマリキーとして使用します。なので、それぞれの ホストは購読情報を1つだけ持つことができます。また、このため、購読情報 の編集に必要な情報は、ユーザ名とホスト名だけです。事実、 editSubscriptionアクションはリクエスト中のユーザー名とホスト名を与える ことで、購読を作成・編集・削除できるように設計されています。 LinkSubscriptionTagの最終目的は、以下のようなブロックを出力することで す:

<A HREF=[path]editSubscription.do?action=[action]&username=[user]&host=[host]">[action]
</A>

based on input block like:

これは以下のブロックを入力としています:

<app:linkSubscription
page="/editSubscription.do?action=Delete">Delete
</app:linkSubscription>

オーバーヘッドを減らすため、LinkSubscriptionTagは 「subscription」を(iteratorが「ID」として参照する)デフォルトの名前とし て使用します。なので、これはタグのプロパティから取り除くことができます。 「action」の部分は異なっており、タグに対するpageプロパティとして与えら れます。

以下、LinkSubscriptionTag.javaから注釈付きでいくつ かのコードを抜粋します:

  1. 文字列バッファを作成し、アプリケーションに対する相 対パスをリクエストに対して問い合わせます。

    StringBuffer url = new StringBuffer(request.getContextPath());
  2. (この繰り返しのための)購読情報beanの参照を取得します。

    subscription = (Subscription) pageContext.findAttribute(name);
  3. ユーザ名とホスト名をbeanからパスへのリクエストの末尾に付加します。

    url.append("&username=");
    url.append(BeanUtils.filter(subscription.getUser().getUsername()));
    url.append("&host=");
    url.append(BeanUtils.filter(subscription.getHost()));

  4. [訳注: BeanUtilsクラスはStrutsのバージョン1.0Finalではdeprecatedになっ ています。現在のサンプルプログラムのコードではBeanUtilsクラスの代わり にResponseUtilsクラスが使われています]

これらの処理は中心的な部分ですが、実用的なアプリケー ションをうまく作るために必要な、残りのエラー処理とロジックのチェックの 部分については、LinkSubscriptionTag.javaのソースコード全体に目を通して 確かめてください。

一方、registration.jspの後ろの方では、ページ中にも う1つのリンクがありますが、こちらはもう1つのカスタムタグ「app:linkUser」 を使っています。

<app:linkUser page="/editSubscription.do?action=Create">
<bean:message key="registration.addSubscription"/>
</app:linkUser>


[訳注: このコード例はStruts1.b1以前に付属する古いサンプルプログラムに基づいています。 b2以降では以下のように修正されています。
<html:link page="/editSubscription.do?action=Create" paramId="username"
paramName="registrationForm" paramProperty="username">
<bean:message key="registration.addSubscription"/>
</html:link>
]

ここまで来れば、あなたはコンフィギュレーション・ファ イルを見ずとも、LinkUserTag.javaを直接見てみよう、という心構えができ ていることでしょう...

LinkUserTag.java

一般的には、LinkUserTagとLinkSubscriptionTagは同じ 問題を解いているわけなので、LinkUserTagタグではiterationからユーザbean を取り出す代わりに、セッションコンテキストから購読情報beanを取得する、 という点を除くと両者は良く似ています。 LinkSubscriptionTagのように、ユーザbeanの名前(例えば"user")はデフォル ト値になっており、タグから省略することができます。必要なものはpageプロ パティだけで、あとは全自動です!

<app:linkUser page="/editSubscription.do?action=Create">
<bean:message key="registration.addSubscription"/>
</app:linkUser>


これをレンダリングすると、以下のようなハイパーテキストリンクが表示されます:

<a href="/struts-example/editSubscription.do?action=Create&amp;username=user">
Add
</a>

アンパサンド(&)記号を含むアンカーリンクは、 LinkUserTagがここでしているように、文字エンティティ&amp;を使って表現しなければならないことに注意。( http://www.w3.org/TR/html401/appendix/notes.html#h-B.2.2)。

さて、この"add"リンクをたどって、editSubcriptionア クションで何が起きているかをともかくも見てみましょう。

EditSubscriptionAction.java

struts-config.xmlの中に、今となってはもう見慣れて いることでしょうが、いくつかの予想どおりのマッピングを見つけることがで きます

<!-- Subscription form bean -->
<form-bean
name="subscriptionForm"
type="org.apache.struts.webapp.example.SubscriptionForm"
/>

<!-- Edit mail subscription -->
<action path="/editSubscription"
type="org.apache.struts.webapp.example.EditSubscriptionAction"
name="subscriptionForm"
scope="request"
validate="false"
>
<forward name="failure" path="/mainMenu.jsp"/>
<forward name="success" path="/subscription.jsp"/>
</action>


以前これらのマッピングを紹介したときには、 struts-config.xmlはActionServletの初期化時に解析される、と述べました。 しかし、ここで我々は、Struts digesterがこのファイルを解析するとき、 (それは標準的なJavaオブジェクトとして生成されますが)コントローラに対 してプロパティとして結び付けられる、ということを明らかにしておくべきで しょう。このことは、「新しい」ステートメントの分岐を追加しただけでは、 Javaのソースファイルを編集しなくてもよいことを意味します。(なんてクー ルなのでしょう)。

struts-config.xmlの指定に従い、コントローラは subscriptionForm beanがSubscriptionActionオブジェクトとともに存在する ことを確認します。次に、オブジェクトのperformメソッドを呼び出します。 performメソッドでは、最初にユーザがログインしていることをチェックしま す。もしそうでなければ、制御はloginアクションに転送されます。 EditSubscriptionAction.performは、新しい購読情報オブジェクトを作り(処 理がCreateである場合)、あるいはユーザーの購読情報ハッシュテーブルでマッチ するホスト名を検索します(処理がEditである場合)。

最終的には、EditSubscriptionActionは ActionForm beanをデータベースbeanに適応させます。データベースにはいくつかの購読情 報があるかもしれませんが、EditSubscriptionActionではこのリクエストで使 用するために選択された(あるいはちょうど作られた)ものを表示します。 ひとたびアクションフォーム(コード上は「subform」と呼ばれる)が作られて、 データベースによって設定されたら、beanのアクションはCreateあるいはEdit にセットされ、制御がsubscription.jspの「success」フォームにフォワード されます。

このservletは、それぞれのアクションに対してオブジェ クトを1つ作ることに注意してください。それぞれのリクエストは別のスレッ ドで処理され、個別のアクションオブジェクトのインスタンスに渡されます。 このことが意味するのは、アクションオブジェクトはスレッドセーフになると いうことです。

さて、ここで最後のJSPを説明するまえに、我々のデータベースモデルについて説明しましょう。

User.java and Subscription.java

あなたがリレーショナルデータベースを使っての仕事に 慣れているのなら、ユーザと購読情報オブジェクト間のリンクは紛らわしいか もしれません。従来のリレーショナルデータベースでは、2つの別のテーブル、 つまりユーザ用のテーブルと、購読情報用のテーブルを作り、それらをユーザ IDで関連付けるでしょう。このサンプルアプリケーションでは異なったモデル: 階層データベースを実装しています。 ここでは、購読情報の「テーブル」は それぞれのユーザオブジェクト中に保存されます。これはまあ、ファイルシス テムがドキュメントをフォルダ中に保存するのと同じようなやり方です。

通常のsetter、getterメソッドに加えて、ユーザオブジェ クトは購読情報オブジェクトと協調動作するための2つのメソッド、 findSbscriptionおよびgetSubscriptionsを持っています。findSubscription メソッドは引数としてhost名をとり、そのホストに対するsubscriptionオブジェ クトを返します。getSubscriptionsはユーザに対するすべての購読情報の配列 を返却します(これはiterateタグで使うのに適しています)。このオブジェク トは、SubscriptionFormデータを管理するために必要なフィールドのほかに、 自身のユーザオブジェクトへの実行時のリンクを維持します。

新しい購読情報を作るために EditSubscriptionAction.javaがするのは、単に新しい購読情報オブジェクト を作成し、そのユーザをリクエスト中のオブジェクトにセットするということ です。そして入力フォームであるsubscription.jspに制御をフォワードします。


subscription.jsp

さて、最後まで取っておいたお楽しみであるところの、subscription.jspでは 興味深いStrutsカスタムフォームタグ「html:options」と「html:checkbox」 をデモンストレーションしています:

registration.jspでは、 購読情報のリストを書き出す のにStrutsのiterationタグが使われていました。iterationタグとcollection タグが便利につかえる他の場所としては、HTML selectタグの選択肢のリスト のところがあります。このように使うのは良くあることなので、Strutsが提供 するhtml:options(optionsは複数形)タグはオブジェクトの配列をパラメータ にとります。このタグは配列(beans)要素のメンバ上で繰り返しを行い、標準 のoptionタグ中にそれぞれを配置します。つまり、以下のようなブロックに対 して

<html:select property="type">
<html:options
collection="serverTypes"
property="value"
labelProperty="label"
/>
</html:select>

Strutsは以下のようなブロックを出力します

<select name="type">
<option value="imap" selected>IMAP Protocol</option>
<option value="pop3">POP3 Protocol</option>
</select>

ここで、1つのコレクションは同名のプロパティから由 来する、labelsとvaluesを含んでいます。Optionsは、それらがvaluesにマッ チしないのなら、labelsのために第二の配列を使うことができます。Options は、リストの元となるデータとしてCollection、Iterator、およびMapを使う ことができます。

デモンストレーション目的で、serverTypes配列がこの ページの最上部に作られます。通常、html:optionsタグは他のところで保守さ れているデータベースから正当な項目をリストアップするために使われます。 例えば、もしアプリケーションがデフォルトの購読情報を選ぶことを必要とするな ら、フォームはoptionsタグを使って購読情報をリストアップするかもしれま せん。

デモンストレーション配列を作るために使われているLabelValueBeanは、単純 で有用なbeanオブジェクトのサンプルにもなっています。

特にトリッキーなHTMLコントロールはチェックボックス です。チェックボックスの問題は、チェックされているときだけリクエスト中 に送られる、ということです。もしチェックされていないなら、それは送られ ません(すなわち空です)。このことは、フォームデータがbeanに変換された後、 フォームデータにバリデーションをかけようとしたときに問題になりえます。 購読情報のためのautoconnectプロパティは、チェックボックスのバリデーショ ンをどのように処理するべきか、ということをデモしています。

SubscriptionForm.java

Strutsのバリデーション機能は、ActionForm beanの resetメソッドおよびvalidateメソッドで操作されます。あなたが自分のフォー ムbeanを作成する場合、ActionFormをサブクラス化し、あなたのフィールドを 追加し、それらに対するgetter/setterメソッド、resetメソッドおよび validateメソッドを実装してください。

Strutsはフォームのプロパティ設定を開始する前に resetメソッドを呼び出します。また、設定を終了した後、アクションの performメソッドの呼び出しの前にvalidateメソッドを呼び出します。resetメ ソッドはフォームの各フィールドにデフォルト値(通常はnull)を代入すべきで す。しかし、チェックボックスの場合には、デフォルト値はnullの代わりに通 常falseになります。

フォームバリデーションの他の例については LoginForm.javaとRegistrationForm.javaを参照ください。

subscription.jspの後ろの方に、カバーしておかないと いけないもう1つのブロックがあります。同じ基本的なフォームを、購読情報 を生成したり、編集したり、あるいは削除したりするといったことに持ちまわ して使えますが、それぞれの場合においてボタンには異なるラベル付けがなさ れることを期待するでしょう。subscription.jspは、それぞれの場合について、 異なるボタンの集合を出力するためにlogicタグを使うように調整してくれま す。これは、subscription.jspの動作方法を本当に変更しているわけではあり ませんが、ユーザの戸惑いを減らしてくれるでしょう。

<logic:equal
name="subscriptionForm"
property="action"
scope="request"
value="Create">
<html:submit>
<bean:message key="button.save"/>
</html:submit>
</logic:equal>


購読情報削除のためのリクエストの場合、サブミットボ タンは「Confirm」とラベル付けされています。なぜなら、この画面は、タス クをSaveSubscriptionAction.javaにその仕事を送る前にユーザがキャンセル を行える最後のチャンスを意味しているからです。

実際のアクションプロパティはHIDDENフィールドとしてフォーム中に置かれて おり、SaveSubscriptionActionは適切な処理を実行するためにそのプロパティ をチェックします。

SaveSubscriptionAction.java

最終的な停止においては、 EditSubscriptionAction.javaとsubscription.jspが開始したものを終了させ なければなりません。通常のロジックとエラーのチェック後に、 SaveSubscriptionActionはこのリクエストによって処理されている購読情報を 削除もしくは更新し、beanをきれいさっぱりと片付けます。 ここまでくれば、あなたはソースコードを読み通し、ポイントをきっちりと押 さえ、非常に満足していることでしょう。これで私たちのツアーはおしまいで す。あとは復習として、新規ユーザによるアプリケーション初回利用時のユー ザ登録がどう動いているか、コードを追って調べてみるのも良いでしょう。ま たそれぞれの.javaソースファイルとJSPファイルを注意深く読んでおくのも良 いでしょう。というのも、これまでの説明では重要なポイントだけしか説明し ていなかったからです。

要約

-- Ted Husted, 25 December 2000 < support@husted.com >


[訳注: これは上原潤二が翻訳しました。日本語訳に対するコメントがあれば、report@jajakarta.orgに送って下さい。]