PORT4 > Pasta
 

Pasta

概要

Pasta

脱Java Beans

Java Beansを捨てましょう。 Java Beansに依存しないプログラムを書くと、こんなによいことがあります。

  • コンパイル時にエラーが検出されやすくなり、実行時に見つかるバグが減る。
  • リファクタリングが容易になるので、汚いコードが放置されにくくなる。
  • リフレクションによるパフォーマンスの低下が避けられる。
  • 難読化ツールが使いやすくなる。

脱JSP/MVCモデル

JSPとMVCモデルを捨てましょう。 JSP/MVCにはこんな欠陥がありました。

  • 複雑怪奇なフォームビーンというものがあるが、顧客の要求の方がもっと複雑怪奇なので、すぐ破綻する。
  • HTMLともXMLともJavaともつかない奇怪なプログラムを目にすることになる。
  • 大量のデータをJavaBeansに溜め込もうとして画面の表示が止まったり、OutOfMemoryErrorが出る。
  • アプリケーションサーバーによって動作がまちまちだったり、パフォーマンスに著しい差が出ることがある。

で、対案は?

JavaBeansの代わりに、普通のJavaオブジェクトを使います。 「Javaオブジェクト」と「リクエストパラメーター」を自動的にバインディングするようなことはしません、ひたすらJavaでプログラムを書いてください。 心配ありません、「Javaオブジェクト」と「フォーム」を結びつける一見便利そうなツールを使って顧客の複雑怪奇な要求に答えるよりは簡単なはずです...。 その代わり、Pastaは「CLS」という言語を使って、「リクエストパラメーター」と「フォーム」を結びつけることができます。 こんな風に:

  <form method="post" cls-logic="$parameters; |restore;">
    <div>お名前:<input type="text" name="name" value=""></div>
    <div>
      職業:
      <select name="job">
        <option value="1">会社員</option>
        <option value="2">自由業</option>
        <option value="3">自営業</option>
        <option value="4">主婦</option>
        <option value="5">その他</option>
      </select>
    </div>
  </form>  

上のHTMLの cls-logic="$parameters; |restore;" の部分に着目してください。 $parametersは、リクエストパラメータを取って来いという意味です。 |restoreは、このタグの中身を、与えられたリクエストパラメーターを送るように書き換えてしまえ、という意味です。 Pastaは賢いので、「このパラメーターを送るには、フォームがこんな状態だったらいいだろうな...」ということを思い描いてくれます。

CLSは画面をデザインするために使われているCSS(Cascading Style Sheet)によく似た言語です。 詳細な説明はここでは省略しますが、不気味なほどロジックとデザインを完全に分離することができます。 さらにCSSを合わせて利用することで、ロジック・文書構造・デザインを完全に分離できます。

解析済みのXHTML/HTMLに適用するという点でXSLTに似ていますが、CLSは既存のページに「何かを加える」あるいは「余計な物を取り除く」という発想なので、さらに「テンプレート的です」 例えば:

  <b cls-logic="[if: ${switch} == 'off'; |hide-tags;]">ON</b>  
  <b cls-logic="[if: ${switch} == 'on'; |hide-tags;]">OFF</b>
  <textarea cls-logic="[if: ${switch} == 'off'; |attribute: disabled;]">
  </textarea>

上の例ではswitchの値が'off'であればONの周りのbタグが除去され、 textareaにdisabledという属性が加えられます

PastaはMVCにおける「モデル」の代わりに「パイプ」を使います。 パイプの出口にはCLSによるテンプレートが接続され、プログラマが書いたプログラムがパイプの入り口にデータを供給します。 パイプから取り出されたデータは、画面表示使われた後にすぐに破棄されるので、メモリを必要以上に消費しなくてすみます。 また、データの供給とテンプレートの処理は並行して行われるので、画面の表示開始まで、ぐるぐる回る「e」マークや「N」マークを眺めている必要はありません。

画面のフローの制御はどうやるの?と聞かれれば、Pastaは特にこれといって秩序だったものを用意していません。 強いて言えば、はるか昔にチューリングさんが考えた「有限状態機械」のモデルがよいでしょう。 大学で計算機科学を勉強していなくとも、PerlやCでCGIを書くプログラマは、無意識にこのモデルに従っているはすです...。

ただし、Pastaはそれを支援するための、便利なツールを用意しています。例えば...

  1. エラーメッセージの表示
  2. ページング(次のページ、前のページ..といった処理)
  3. multipart/form-dataの処理(ファイルアップロード)

などなど。

Pastaの目的は?

デザインとロジックを完全に分離してサイトのメンテナンスを容易にすること、 巨大なページを安定して出力できるテンプレート言語を作ること、 JavaBeansを排除して、プロプリエタリな商用ソフトとしてのパッケージングを容易にすることです。

使えますか?

使えます。ダウンロードして実行してみてください。 また、実際に使っている現場を見ることもできます。

Credits

This product contains software developed by Apache Software Foundation (http://www.apache.org/).

This product includes software developed by Andy Clark.

Todo List

high

    middle

      ダウンロード

      Pasta

      コンパイルして実行するには?
      1. J2SDK (1.4.2で動作確認しています)をインストールする。
      2. 環境変数JAVA_HOMEをJ2SDKのディレクトリに設定する(Windowsでは不要かもしれません)。
      3. 展開したpastaのアーカイブの中で、"./build.sh run"を実行する。
      4. ブラウザでhttp://localhost:8888/へアクセスする。
      アーカイブ

      0.8.1b - いくつかバグを修正。クッキーをサポートした他、拡張機能用にsrc/extenstionsソースパスを作成。

      0.8b - 分岐処理をifロジックに一本化。

      0.7.2b - Mapperに国際化機能を追加

      0.7b - スクリーン・スタック、Mapperの導入

      0.6.1b - 子孫選択子をサポート

      0.6b

      Pasta フォーラム

      これは何?

      Pastaベースの掲示板アプリケーションです。 .warパッケージなので、そのままサーブレットコンテナに配備し、http://localhost:8080/pasta-forum/forum.htmlにアクセスしてください。 デフォルトではHSQLが使われますが、PostgreSQLにも対応しています。 設定は/WEB-INF/pasta.xconfを見てください。

      アーカイブ

      pasta-forum.war

      FAQ

      FAQ

      このページには、今まであった質問がまとめられています。

      Q. Pushという名前は動詞の名詞的用法としても違和感があります。 ソース、データソース、供給、生産者、サプライ 等の類語ではどうでしょうか?

      A. おっしゃる通り、Push/Pullは私も違和感を感じています。 Producerというクラス名も考えたのですが、 結局どちらも4文字で収まるPush/Pullにしました (辞書で調べると、どちらも名詞として使えるようなので)。 他によい対案が現れるか、そのまま既成事実として定着してしまうか、 しばらく見極めたいと思っています。

      ライブサイト

      ライブサイト

      CSSJ
      掲示板、ライセンスキー発行フォーム等で使われています。
      ブログ出版局
      全面的にFortress / Pastaベースで動いています。

      基本機能

      Pasta/CLS 仕様

      適用対象

      CLSは、ウェブページなどのHTMLおよびXMLベースの動的なページを生成するためのテンプレート言語です。 処理の対象となるテンプレートは、XMLモデルとして扱うことができなければなりません。 すなわち、HTMLであれば、ページの生成の過程で、整形式のXML(XHTML)モデルに変換されます。

      動作モデル

      制御モデル

      CLSはインタプリタ方式の言語です。一般の言語処理系と同様にプログラム・カウンタに相当するものを持ち、 ジャンプ、繰り返し、条件分岐といった制御が可能です。 CLSプロセッサはXML形式のテンプレートの先頭から処理を開始し、各種フロー制御などを経て、 その過程で通過したノードを結果として出力します。 これは、一般的なテンプレート言語と変わりありません。

      制御モデルについての詳細はLogicのセクションで説明します。

      フィルター・チェイン

      CLSの出力結果はXMLとなります。 処理の過程で通過したテンプレート上の内容を順に出力しますが、その前にフィルター・チェインという機構を通ります。 これは、XMLを変換するフィルタを連結させたもので、テンプレートの処理中に動的に変化します。

      フィルター・チェインについての詳細はFilterのセクションで説明します。

      データ供給モデル

      背景

      従来から使われているPerl,CによるCGI、サーブレットや単純なPHPやJSPの方式は「プッシュ型」と呼ばれます。 このような方式ではプログラムによって動的にデータを生成し、固定したHTMLのタグや文章と織り交ぜてクライアントに対して「押し出し」ます。

      それに対して、テンプレートエンジン (Struts, Smarty, Velocityなど)では「プル型」という方式が用いられます。 これはデータの供給元をあらかじめ用意し、テンプレートから「引き出す」方式です。

      一般に後者の方式の方が、データを生成するためのロジックと、テンプレートによるデザインを綺麗に切り分けることができます。 ただし、あらかじめデータを生成する処理に時間がかかったり、データを蓄えるためにメモリを大量に消費することがあるという欠点もあります。

      Pastaは後者の方式を採用しつつ、データ生成にかかる時間と、メモリの使用量を減らすために、 データの生成とテンプレートの処理を並行して行うというアプローチを取っています。

      スクリーン

      スクリーンはPastaのデータ供給モデルの基礎となるコンポーネントです。 これは、名前が付けられたパイプが束ねられた構造になっています。 各パイプは様々なオブジェクトをキューイングすることができ、なおかつスレッドセーフです。 (javadoc参照)

      スクリーンの入り口は、複数のデータを供給するコンポーネントに接続され、出口はテンプレートに接続されています。 すなわち、データ供給コンポーネントによって供給されたデータが製麺機かところてんのように押し出され、テンプレートはそれを消費することになります。 これは典型的な生産者-消費者パターンです。

      Push (生産者)

      Push(javadoc参照) はスクリーン上の1つのパイプに接続し、データを「生産」します。

                          スクリーン           ____________
       Push(1)-data-> |==パイプ(1)==| -data-> |テンプレート|
       Push(2)-data-> |==パイプ(2)==| -data-> |____________|
       ...
      

      全てのPush、そしてテンプレートは並行に動作します。 テンプレートのデータ取り出しよりもPushの処理が先行している場合は、 テンプレートが未取得のデータはスクリーンの各パイプにキューイングされます。

      Pushを使ってテンプレートにデータを供給する方法は一般に、 最初のデータが画面に表示されるまでの時間が短く、体感速度が速くなります。 また、Pushが特に大量のデータを出力する場合は、 テンプレートに表示済みのデータは直ちに破棄できるため、 メモリの使用効率が高くなります。

      ただし、Pushの数だけスレッドを起動するため、 Pushの数が極めて多い場合は、 システムのリソースをかえって消費するという欠点があります。

      Pull (反復子)

      Pull(javadoc参照) は生産者-消費者パターンの生産者には該当せず、一種の反復子(iterator)として動作します。 なおかつ、このコンポーネントはスクリーン中のパイプの出口に「偽装」されます。

         スクリーン           ____________
       x==Pull(1)==| -data-> |テンプレート|
       x==Pull(2)==| -data-> |____________|
       ...
      

      Pushがあるにも関わらずこのようなコンポーネントが用意されている理由は、 Pullにはスレッドを起動しなくてもよいという利点があるためです。 特に、データがあまり多くない場合や、データの供給源が高速である場合は有利です。 実際、そのような状況が多いはずです。

      また、Java言語の場合反復子として動作する既存のオブジェクトの java.util.Iteratorあるいは、 java.sql.ResultSetと接続しやすいため、 Collection系オブジェクトやデータベースからのデータ取得に適しています。

      Module

      Module(javadoc参照) はScreenに取り付き、複数のパイプを占有するコンポーネントです。 HTTPリクエスト・パラメータ、クッキーのような辞書的なデータに一括してアクセスするという、現実的な要求のために用意されたものです。

      Moduleは占有しているパイプにアクセスがあった時点で仮想的な出口(Pullに相当するもの)を生成します。

      Replacer (置き換えタグ)

      置き換えタグは、スクリーンから取り出したデータで置き換えられる文字列です。 ${式}という形式で、テンプレートの属性、テキストなどに記述することができます。 これは多くのテンプレート言語に見られるやり方です。

      式の形式はカスタマイズ可能ですが、普通はパイプの出口にあるデータを表示する場合は${パイプ名} という形式で記述します。 ${#パイプ名}とすると、パイプの先頭の値を取り出し、その値で置き換えられます。

      詳細はReplacerの詳細のセクションで説明します。

      スクリーン・スタック (局所変数?)

      同じスクリーン上で、既にコンポーネント(Push/PullおよびModuleによって生成された出口)が接続されているパイプに、 さらにコンポーネントを接続しようとすると、古いコンポーネントが破棄され、新しいコンポーネントが接続されます。

      しかし、CLSの処理系ではスクリーンは1つではなく、XMLの要素およびブロック構造(後述)ごとにスクリーンが生成されます。 生成されたスクリーンは要素またはブロックが終了した時点で破棄されます。 テンプレートはまず最後に生成されたスクリーンにアクセスを試み、値があった場合はその値を使います。 値がなければ、上位のレベルのスクリーンにアクセスを試みます。 以降、これを最上位まで繰り返します。 一方、各コンポーネントの接続は、最後に生成されたスクリーンに対して行われます。 スクリーンが破棄されると同時に、コンポーネントも破棄されます。

      これは一般的な言語処理系の局所変数スタックに似ています。

      インラインロジック宣言構文(cls-logic属性)

      CLSの構文には、Logic,Filter,Action,PushPull,Moduleというコンポーネントと、制御構造をまとめるBlockがあります。 各要素にcls-logicという名前の属性を付けることで、これらのコンポーネントをXML要素に割り振ることができます。 cls-logicの構文はCSSのstyle属性と非常によく似ていて、"(コンポーネントの種類を表す記号)コンポーネント名前: 値1 値2 ...;"という形式の繰り返しです。 以下にその構文を示します。

      [1] LogicDeclaration ::= (Logic|Action|Filter|Push/Pull|Module|Block)*
      
      [2] Argument ::= Ident|Literal|Expr
                       
      [2.1] Ident ::= [^{space}]*
      
      [2.2] Literal ::= '"'[^"]*'"'
                       | "'"[^']*"'"
                       
      [2.3] Expr ::= '${'[^\{]*'}'
                       
      [3] Name ::= [^:;'"{space}]+
      
      [4] Logic ::= [^@@|=$:;'"{space}]Name ';'
                    | [^@|=$:;'"{space}]Name ':' Argument* ';'
      [5] Action ::= '@'Name ';'
                    | '@'Name ':' Argument* ';'
      [6] Filter ::= '|'Name ';'
                    | '|'Name ':' Argument* ';'
      [7] Push/Pull ::= '='Name('/'Name)? ';'
                    | '='Name('/'Name)? ':' Argument* ';'
      [8] Module ::= '$'Name('/'Name)? ';'
                    | '$'Name('/'Name)? ':' Argument* ';'
      
      [9] Block ::= '[' LogicDeclaration number?']'
      

      以下の例では、要素liに[restore Filter,iterate Logic,not-null Logic]というリストを対応させ、 restoreには空、iterateには["foo","a"]、not-nullには["a"]という引数が与えられます

        <ul>
          <li cls-logic="|restore; iterate: foo a; not-null: a;">
            ${a}
          </li>
        </ul>
      

      各コンポーネントに与えられる引数には、識別子(Ident)、リテラル(Literal)、式(Expr)の3種類があります。 識別子はクオートでくくられない文字列で、リテラルはクオートでくくられた文字列です。 式は${...}という形式のもので、後述する置き換えタグに類似しています。 識別子、またはリテラルは文字列としてコンポーネントに渡されますが、式では式によって得られたオブジェクト(数値、boolean、nullなど)がそのまま渡されます。 引数が明らかに識別子として認識されない状況では、識別子はしばしばリテラルとして解釈されます。 また、引数が識別子かリテラルとして認識されるかが曖昧な状況では、式の値は識別子として認識されることが優先されます。 確実にリテラルとして式を認識させる場合は、リテラル内に置き換えタグを記述した形式 '${...}' を用います。

      コンポーネントの機能の詳細

      Logic (制御構造)

      Logicは、プログラミング言語の制御文に相当するもので要素あるいはブロック(後述)単位で非表示・表示・繰り返しを行います。 以下は、cls-logic属性によって要素にLogicのリストを対応させた例です。

        <ul>
          <li cls-logic="present: foo ; iterate: foo a; not-null: a;">
            ${a}
          </li>
        </ul>
      

      この記述は、li要素にLogicのリスト[present Logic,iterate Logic,not-null Logic]を対応させます。

      CLSプロセッサがこの要素を見つけると、対応させているLogicのリストを先頭から実行します。 上の例では、presentが最初に実行を開始します。 このとき、present Logicは以下の3つの動作のうち1つをCLSプロセッサに指示することができます。

      何もしない
      CLSプロセッサは、何もせずに次のiterate Logicを開始します。
      スキップする
      CLSプロセッサは、後のLogic(iterate,not-null)を飛ばして、li要素の終了タグの直後に処理を移します。
      現在の位置をマークする
      CLSプロセッサは現在の位置(li要素の1番目のLogic)を記憶して、次のiterate Logicを開始します。

      Logicの実行開始時に何もしないか、内容をスキップした場合は、直ちにLogicの実行は終了します。 現在の位置をマークしてli要素の直後に達すると、さらに2つの選択肢を選ぶことができます。

      何もしない
      present Logicは終了し、そのまま処理を続行します。
      マークした位置に戻る
      present Logicを終了せずにli要素の1番目のLogicの次のLogic、つまりiterateに処理を移します。 li要素の後に到達したとき、present Logicは再び、何もしないか、マークした位置に戻るか選ぶことができます。

      iterate、あるいはnot-nullが現在位置をマークした場合は、li要素の後で起こる処理の選択は、Logicが実行を開始したのと逆順に行われます。

      cls-logicに記述された制御を開始した時点では、その要素自身の開始タグは出力されないことに注意してください。 かつ、Logicによる制御の対象はその要素の終了タグの直後までです。 Logicが要素をスキップする場合、その要素自身が表示されないことになり、 繰り返しが行われる場合は要素自身が繰り返されます。

      Action (状態の変化)

      '@'(actionのa)という記号はActionを表します。 Actionは実行時にバックエンドでなんらかの変化を起こさせるものです。 それ自身が直接制御の流れを変えることはありませんが、状態の変化が後のコンポーネントに影響を与えます。

      以下の例ではp要素に[load Action,present Logic,load Action]というリストが対応付けられています。

        <p cls-logic="@load: foo ; present: foo; @load: bar;">
          ${#bar}
        </p>
      

      仮に、load Actionは引数で与えられた名前のパイプに(例えばデータベース等から)値を読み込み、 present Logicは引数で与えられた名前のパイプに値が存在すれば処理を続行し、存在しなければ処理をスキップするとします。

      上の例では最初にパイプfooに値が読み込まれ、present Logicがfooに値が存在するのを確認して処理を続行し、さらにbarに値が読み込まれます。 そして、${#bar}という置き換えタグがbarの値で置き換えられます。

      最初のload Logicがfooへの値の読み込みに失敗したらどうなるでしょうか? この場合、present Logicは処理をスキップし、p要素の後に制御が移ります。 よって、@load: bar;というActionは実行されないことになります。

      この例のように、Action,Logic,Actionという並びでは、Logicの後のActionがLogicによる制御の影響を受けます。 仮にpresent Logicが繰り返しを行ったとすれば、@load: bar;が複数回実行される可能性がありますが、 @load: foo;は必ず1度しか実行されません。

      後で解説するPush/Pull,Module,Filterを表す記号('=','$','|')は実際はスクリーン・スタックまたはフィルター・チェイン対する状態変化を起こさせるActionとして機能します。 ただし、スクリーン・スタックはブロック単位で、フィルター・チェインは要素単位で追加されたコンポーネントが有効になることに注意しなければなりません。

      Filter (出力結果のフィルタリング)

      '|'(U*IXのshellのパイプ記号)という記号はFilterを表します。 Filterは要素単位で内容をフィルタリングします。 CLSプロセッサがテンプレートを処理するとき、制御の道筋で通過したノードを出力します。 そこにFilterが存在した場合、CLSプロセッサは結果の出力をFilterに振り向けます。 1つ、あるいは複数連結されたFilterの末端の出力が最終的な出力結果ということになります。 以下のテンプレートは、li要素にpresent Logic,text Filter,unescape Filterというリストを対応させる例です。

        <ul>
          <li cls-logic="present: foo ; |text: '<b>${a}</b>'; |unescape;"></li>
        </ul>
      

      初期状態では、CLSプロセッサの制御が通過した部分のノードがそのまま出力されます。

      入力XML -> CLSプロセッサ -> 出力XML

      present Logicが内容をスキップしなかった場合、次のtext Filterに制御が移り、フィルター・チェイン(括弧でくくられた部分)にtext Filterが連結されます。

      入力XML -> CLSプロセッサ -> (text) -> 出力XML

      さらに、unescape Filterが連結されます。

      入力XML -> CLSプロセッサ -> (text -> unescape) -> 出力XML

      このとき、入力された要素liとその内容は、textロジックによって変換され、さらにunescapeロジックによって順に変換されます。

      要素liの末端(終了タグ)に達すると、追加されたのと逆の順番でunescape,textフィルタが外されます。 そして、前に述べたとおり、Logicは何もせずに終了するか、繰り返すかを選択することができます。

      以下のように、Filter,Logic,Filterの順で指定されている場合、Actionと同様にLogicの後に記述されているFilterだけがLogicによる制御の影響を受けます。 すなわち、present Logicがどのような振る舞いをしてもtext Filterがli要素に適用されますが、 present Logicがli要素を飛ばすという判断を下した場合、unescape Filterはフィルター・チェインに追加されません。 (もっともこの場合li要素自身が出力されないのでtext Filterによるフィルタリングは「空振り」することになります。)

        <ul>
          <li cls-logic="|text: '<b>${a}</b>'; present: foo; |unescape;"></li>
        </ul>
      

      以下は、ul要素にtext Filterを適用し、内部のli要素にpresent Logicとunescape Logicを適用する場合です。

        <ul cls-logic="|text: '<b>${a}</b>';">
          <li cls-logic="present: foo; |unescape;"></li>
        </ul>
      

      ul要素が開始した時点では、フィルター・チェインにはtext Filterが連結された状態になります。 さらに、li要素でpresent Logicが制御をスキップしなかった場合、さらにunescape Filterが連結されます。 unescape Filterはli要素の終了時に外され、text Filterはul要素の終了時に外されます。 結果として、ul要素はtext Filterの影響を受け、li要素はtext Filterとunescape Filterの両方の影響を受けることになります。

      フィルター・チェインはhttp://sax.sourceforge.net/イベントで連結され、 Chain of Responsibility(CoR)パターンによりパイプライン処理されます。

      CoRによるパイプライン処理を行うという点はApache Cocoonの設計概念によく似ています。 CLSは要素単位でパイプラインの構成を動的に切り替えられるのが大きな特徴です。

      Push/Pull (データの供給・反復)

      '='(UN*Xのls -Fコマンドのソケット記号)という記号は、パイプに対してPushまたはPullを接続するための一種のアクションです。 PushとPullの両方に同じ記号が使われるのは、どちらもテンプレート側からはパイプにデータが供給されているように見えるため、 区別する必要がないためです。

      以下の例を見てください。Push/Pullの記述で'/'に続く部分は、データが供給されるパイプの名前です。 省略すると、Push/Pullの名前自体がパイプ名になります。

        <ul>
          <li cls-logic="=load/foo; iterate: foo; =change: foo;">${change}</li>
        </ul>
      

      iterateが引数で与えられたパイプに存在する値の数だけ処理を繰り返すものとすると、 =load/foo;によってfooへ供給される値が尽きるまで=change: foo;の実行と、 li要素の表示を繰り返すことになります。

      以前に説明したActionでもパイプにデータを押し込むことはできますが、 同じパイプに何百、何千というデータを押し込んで後で繰り返す場合、 Actionの実行中に一時的にページの生成がストップしてしまうことになります。 Push/Pullを使うと、全てのデータが供給される前にページの生成を開始できるという点でActionより優れています。

      Module

      '$'(ScreenのSに取り付く)の記号は、パイプに対してModuleを取り付かせるための一種のアクションです。

      Push/Pullは1つだけのパイプと結びつくことができますが、Moduleは複数のパイプに対するアクセスを監視し、 アクセスがあった時点でデータの供給を開始します。

      以下の例は、"par."で始まる名前のパイプに対するアクセスを監視します。

      <body cls-logic="$parameters/par;">
        こんにちは${par.name}さん。
      </body>
      

      '/'に続く部分は監視するパイプの名前の先頭部分で、実際には後に'.'が付加されます。 これを省略すると、Moduleの名前自身が使われます。 Moduleが結び付けられた時点では何も起きませんが、${par.name}という置き換えタグが "par."で始まる名前のパイプにアクセスしているので、この時点でデータの供給が開始されます。

      仮に$parameters/par;は"par.リクエストパラメータ名"という形式のパイプ名へのアクセスに反応し、 対応するリクエストパラメータの値を供給するものとすれば、 例のページはnameパラメータに渡された人名を表示して、その人に挨拶することになります。

      ブロック構造

      今までの説明ではLogicによる制御の単位はXMLの要素でしたが、 ロジック宣言内の一部を'[]'で囲むことにより、ブロック構造を作ることができます。 ブロック構造によって、ロジック宣言内でさらに処理を制御できるようになります。

      通常、Logicは要素の終了タグの直後までの処理を制御しますが、 ブロック内に書かれたLogic、ブロックの末端までを制御の対象とします。

      以下の例では、present Logicによって|include: b;の実行・スキップ・繰り返しが行われますが、 ブロックの外の=load/a; iterate: a;は影響を受けません。

        <a cls-logic="[iterate: b; |include: b;] =load/a; iterate: a;" href="${a:uri}">${a:title}</a>
      

      さらに多くのプログラミング言語と同様に、ブロックの脱出レベルを指定することができます。 ']'の直前に数値を記述すると、ブロック終了時その数だけ上位ブロックを飛ばします。 数値の後にスペースを入れてはいけません。 以下の例では、ブロック内の|include: b;が実行された場合はトップレベルブロックがスキップされるため、 ブロックの外の=load/a; iterate: a;は実行されません。

        <a cls-logic="[iterate: b; |include b; 1] =load/a; iterate: a;" href="${a:uri}">${a:title}</a>
      

      これはPHPのbreak文によく似ています。

      ロジックシート

      cls-logic属性によるインラインロジック宣言よりも、このセクションで説明するロジックシートを使うことを推奨します。 それには、以下の2つの理由があります。

      1. ロジックを完全にテンプレートから排除するので、テンプレートの可読性が上がる。
      2. ロジックシート内のロジック宣言が事前コンパイルされるため、パフォーマンスが上がる。

      ロジックシートをテンプレートに結び付けるには、2種類の方法があります。 まず、テンプレート中の任意の場所で、cls-logic要素内に記述することです。 cls-logic要素が終わった直後のノードからロジックシートが有効になるため、 それ以前の部分には影響しません。 そのため、例えば以下のようにページのなるべく先頭に記述することをお勧めします。

      <html>
        <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
          <cls-logic>
            ...ロジックシート...
          </cls-logic>
          <title>タイトル</title>
        </head>
        <body>
          ...
        </body>
      </html>
      

      ロジックシートはCSS(カスケーディングスタイルシート)と同様の形式で記述します。 ただし、CSSのセレクタに相当するものは、クラスセレクタと子孫セレクタだけをサポートします。 (将来拡張される可能性がありますが、適用対象がほとんどの場合HTML/XHTMLという現状では、 要素セレクタなどはあまり役に立ちません。)

      また、CSS同様のディレクティブを使うことができます。用意されているディレクティブは@charsetと@importです。 ディレクティブは必ず、全てのセレクタの前になければなりません。 以下に構文を示します。 (@importディレクティブの引き数にurl(...)は使えず、文字列を使うことに注意してください。)

      [10] LogicSheet ::= Directive* Selector*
      [11] Directive ::= '@import' Argument ';'
                        |'@charset' Argument ';'
      [12] Selector ::= '.'ClassName '{' LogicDeclaration '}'
      [13] ClassName ::= {name}
      

      テンプレート中で要素にクラスを対応させるには、cls-class属性を用います。 cls-classの値は、ロジックシートに存在するクラス名です。

      以下のli要素はcls-logic="|unescape: foo; iterate: foo a; not-null: a;" という属性を持ったのと全く同じ振る舞いをします。

        <cls-logic>
        .foo {
          |unescape: foo;
          iterate: foo a;
          not-null: a;
        }
        </cls-logic>
        
        <ul
          <li cls-class="foo">
            ${a}
          </li>
        </ul>
      

      CSS同様に、ロジックシートは、外部のファイルとして分離することができます。 cls-import要素は、href属性によって指定した外部のロジックシートをテンプレートに対応させるもので、 指定されたロジックシートをcls-logic要素によって埋め込んだのと同じ振る舞いをします。

      以下の例では、module.clsをロジックシートに用います。

        <cls-import href="module.cls">
      

      また、importディレクティブを以下のように使った場合も同じ振る舞いをします。

        <cls-logic>@import: 'module.cls';</cls-logic>
      

      XML文書のルート要素外でロジックシートを指定するために、xml-logicsheet処理命令が用意されています。 上記の2つのインポート処理は、以下の記述でも代用することができます。

        <?xml-logicsheet href="module.cls"?>
      

      以下は外部のロジックシートの例です。 ロジックシートファイルのデフォルトのキャラクタエンコーディングは"UTF-8"です。 他のキャラクタエンコーディングを用いる場合は、@charsetディレクティブで指定します。

        @charset "ISO8859-1";
      
        .foo {
          |unescape: foo;
          iterate: foo a;
          not-null: a;
        }
      

      スクリプト

      これは、ロジック宣言を要素と関連させずに、単なるスクリプトとして実行する機能です。 スクリプトはcls-action要素内に記述されます。 形式はロジック宣言に似ていますが、対応する要素がないため、Filterを使うことはできません。 制御構造は、cls-action要素の開始タグの直後から、cls-action要素の終了タグの直前までになります。

      [14] Script ::= (Logic|Action|Push/Pull|Module|Block)*
      

      Replacerの詳細

      置き換えタグが適用されるのは、1) XMLのテキストノード 2) XMLの属性の値 3) CLSコンポーネントの与えられる引数 です。 このうち、CLSコンポーネントの引数に置き換えタグを使う場合、必ず引用符でくくらなければならないことに注意してください。

      以下は置き換えタグの使用例です。

        <cls-logic>
        .foo {
          |unescape: foo;
          iterate: foo a;
          not-equal: '${a}' '$$';
        }
        </cls-logic>
        
        <ul>
          <li cls-class="foo">
            <a href="${a}">${a}</a>
          </li>
        </ul>
      

      置き換えタグ内の式の形式は特に規定されません。 PastaのReplacerコンポーネントを切り替えることでカスタマイズ可能です。

      用意されているReplacerの構文はReplacerのドキュメントを参照してください。

      サイレントモード

      テンプレートのルート要素に設定可能なcls-silent属性はエラーの際に処理を続行するかどうかを設定します。

      trueに設定すると、clsの構文エラーなどが発生した際に、処理を続行します。 このとき、CLSプロセッサはエラーの内容をログに出力できます。

      falseに設定すると、エラーの際は直ちに処理を中断します。 大抵の場合は、ウェブサーバーによってユーザーにエラーメッセージが表示されます。

      最終的な出力で除去される内容

      CLSプロセッサは、最終的に出力される内容から、CLS特有の属性や要素を全て除去します。 そのため、最終的名出力結果は標準に忠実なHTMLとすることが可能です。

      cls-span要素は、テンプレートの構造上のブロックを設定するためのものです。 HTMLのspan要素と異なり、最終的な出力では消去できるため、文書の妥当性を壊すことがありません。

      cls-mark要素は、cls-span要素と同様の働きをしますが、こちらは終了タグがありません。

      XML名前空間による拡張

      CLSに関連する要素、属性を"http://port4.info/2002/CLS"という名前空間に分ける方法が用意されています。 この記述方法は、XHTMLなどのXML文書で文書の妥当性を壊さないという利点があります。

      以下の表は、事前にxmlns:cls="http://port4.info/2002/CLS"という宣言がされているものとして、各要素、属性の記述方法を説明したものです。

      要素名名前空間付きの記述使用場所終了タグ
      cls-importcls:importheadまたはbody要素内なし
      cls-logiccls:logicheadまたはbody要素内あり
      cls-actioncls:actionheadまたはbody要素内 あり
      cls-spancls:span文書中のどこでもあり
      cls-markcls:mark文書中のどこでもなし
      属性名名前空間付きの記述
      cls-logiccls:logic
      cls-classcls:class
      cls-silentcls:silent

      Pastaの起動

      Pastaを実行したいんですが...

      PastaはApache Excalibur Fortress コンテナ・フレームワーク上で動作します。 Pastaを実行するためには、Fortressを起動することになります。

      Fortressをいじったことのある方は、このページの説明を見ながら、いろいろな実行形態を模索することができるでしょう。 しかし、Fortressにつてよく分からない方は、とりあえず実行していろいろいじくってみることをお勧めします。 まずはPastaサーブレットに書かれていることを試してみてください。 そして、Action,Pullなどのコンポーネントを自分で作って追加してみたくなったときに、このドキュメントを読み直してください。

      Fortressの概要

      要は、いろいろなクラストを登録しておいて、プログラム内で必要に応じて取り出して使えるようにするライブラリです。 なぜこんな物を使うのかというと、一口にクラスと言っても、1つのインスタンスをプログラムのあちこちで共有できる場合と、 その都度new演算子でインスタンス化しなければならないことがあり得ます。 例えば、単純に1回のメソッド呼び出しで住むような計算を行うクラスはインスタンスをあちこちで共有して構いませんが、 データベース接続のような、複数のメソッド呼び出しを伴うクラスのインスタンスを共有すると、とんでもないことになります。 かといって、何でもかんでも毎回newするのは非効率的です。

      Fotressにクラスを登録するとき、そのクラスのインスタンスはどのような性質を持つのか (あちこちで共有できるのか?できないのか?使いまわしは効くのか?など)という情報を与えておきます。 そうすれば、Fortress勝手に管理してくれるので、プログラマは自前で管理する必要がなくなります。

      例えば、HogeActionというActionを"hoge"という名前で登録するためには、 設定ファイルに次のような記述をするでしょう。

        <hoge-action id="hoge"/>
      

      id属性は、プログラム(おそらくはPastaのCLSプロセッサ)がアクセスする際のキーになります。 hoge-actionという要素をHogeActionというJavaのクラスに結びつける設定(メタ情報)が別途必要なのですが、 後のセクションでそれを説明します。

      Fortressには、ここに書かれている以外にも様々な機能があるのですが、そのあたりは本家のサイトを見ながらハックしてください。

      Meta Info?

      Fortressには、かつでXDocletと呼ばれたJavadocもどきを使って、 メタ情報を生成する便利なツールが含まれています。 例えば、作成したクラスのコメントに以下の記述をします。

      /**
       * <p>
       * 各Actionについての説明は <a
       * href="http://www.port4.info/pasta/basic/action.html">ドキュメント </a> を参照してください。
       * </p>
       * 
       * @avalon.component
       * @avalon.service type="Action"
       * @x-avalon.info name="action-default-to"
       * @x-avalon.lifestyle type="singleton"
       * 
       * @author <a href="mailto:miyabe@port4.info>MIYABE Tatsuhiko </a>
       * @version $Id$
       */
      public class DefaultToAction extends AbstractAction implements ThreadSafe {
      ...
      

      これで、設定ファイル内にaction-default-toという要素名でこのクラスの設定が書けるようになります。

      この方法は非常に便利であり、Pastaで利用することもできるのですが、 実際は別のアプローチを取っています。

      この類の分散管理は、あらかじめ仕様をきっちり定める人には便利かもしれませんが、 躊躇なくリファクタリングをかけ、クラスやパッケージ名の変更や移動をやりまくるような優柔不断な人 が使うと、不要になった、あるいは動かないコードまであちこちに分散することになります (作者は最近流行のアスペクト指向というものに懐疑的でもあります...)。

      また、もっと実際的な問題として、このツールはソースからクラス名のリストが格納されたファイルを生成するので、 後で難読化ツールのような、 派手にバイナリを加工し、クラス名を書き換えてしまうようなツールと一緒に使うのが難しいでしょう。

      Bootstrap

      Mata Infoツールを使う代わりに、プログラムの開発者は、 Bootstrap インターフェイスを実装したクラスを用意し、その中で、クラスと設定ファイル中の要素名とクラスの性質をプログラミングしてください。 記述方法は、info.port4.pasta.samples.bootstrap.SampleBootstrapのソースを参考にするのがよいでしょう。

      作成したBootstrapの実装を呼び出す例はPastaサーブレットがあります。 クラス名を文字列として記述するのはこれだけなので、難読化ツール等を使う場合はBootstrapの実装クラスだけを除外すればよいことになります。

      ロジック

      概要

      Logicは条件分岐や繰り返し処理を実行するためのものです。

      作成方法

      Logic インターフェイスを実装したクラスを作成します。 あるいは、 AbstractLogic を継承します。

      リファレンス

      if
      C/Javaライクな論理式を評価します。 値が偽の場合は、処理をスキップします。 利用可能な演算子は ==(等価), !=(非等価), !(否定), &&(論理積), ||(論理和),です。 それぞれ eq, neq, not, and, or と記述することもできます。 各トークンの間は、必ずスペースで区切ってください。 また、括弧()が利用可能です。
      例: (${a} || ${b}) && ${c} = '1'
      while
      ifロジックと同様の式を評価しますが、値が真の間繰り返します。 このロジックは無限ループに陥る可能性をはらんでいるため、繰り返し回数は制限されます。 繰り返し回数の制限はpasta-basic.xconfに記述されています。
      for
      key* ['rotate'|'remove']
      複数のkeyで指定された全てのパイプの値がなくなるまで処理を繰り返します。 繰り返しの最後で全てのパイプの先頭の値を破棄します。 'rotate'が指定されていると全ての繰り返し終了後に全てのパイプの状態を復活させます。 'remove'が指定されているとパイプの値は破棄されたままです。 デフォルトは'remove'です。
      iterate
      key i ['rotate'|'remove']
      パイプkeyの値がなくなるまで、値をパイプiに移動させて処理を繰り替えします。 パイプiの値は繰り返しごとに破棄されます。 forロジックとの違いは、対象となるパイプは1つであることと、媒介変数を使用するところです。 'rotate'が指定されていると全ての繰り返し終了後にパイプkeyの状態を復活させます。 'remove'が指定されているとパイプの値は破棄されたままです。 デフォルトは'remove'です。

      フィルター

      概要

      Filterは、テンプレートを要素単位でフィルタリングするものです。

      作成方法

      Filter インターフェイスを実装したクラスを作成します。 あるいは、 AbstractFilter を継承します。

      リファレンス

      tags
      TODO
      text
      text
      要素内のテキストをtextに置き換えます。
      attribute
      attrName
      要素に名前と値が共にattrNameである属性を追加します。
      attrName attrValue
      要素に名前がattrName、値がattrValueである属性を追加します。
      hide-attributes
      attrName1 attrName2 ...
      要素から引数で与えられた名前の属性を消去します。
      hide-tags
      なし
      要素の開始タグと終了タグのみを消去します。内部のテキストや要素は残されます。
      trim-text
      TODO
      text-to-html
      TODO
      unescape
      引数なし
      要素内のHTML/XMLのためのエスケープを解除します。
      include
      uri
      uri 'inherit'|'new'で2番目の引数にinheritを指定するのと同じです。
      uri 'inherit'|'new'
      要素内にuriで表される他のページを挿入します。uriの形式についてはMapperの説明を参照してください。 2番目の引数にinheritを指定するとスクリーンを継承します。 すなわち、現在のページで生成されたデータを挿入するページ内でも使うことができます。 newを指定すると、新しいスクリーンを生成します。
      restore

      スクリーンをHTTPリクエストパラメータに見立て、そのようなリクエストを送るフォームの状態を再現します。 フォームに関連するタグ名は、全て小文字でなければなりません。 幸い、Pastaに付属するHTMLパーサーはデフォルトで全てのタグ名を自動的に小文字に変換してくれます。 XMLを使う場合は、自動的には小文字に変換されないの注意してください(XHTMLではタグ名は全て小文字で書くように規定されています)。

      このFilterの使用例、しくみについてはHTMLフォームを参照してください。

      引数なし
      パラメータ名に対応するパイプとして、パラメータ名の頭に"parameters."を付けた名前のパイプを使います。
      prefix
      パラメータ名に対応するパイプとして、パラメータ名の頭にprefixに'.'を付けた名前のパイプを使います。
      switch expect
      パラメータ名に対応するパイプとして、パラメータ名の頭に"parameters."を付けた名前のパイプを使います。 switchはパラメータの名前で、この(パイプに格納された)パラメータの値が文字列expectと等しいとき復元を行います。
      prefix switch expect
      パラメータ名に対応するパイプとして、パラメータ名の頭にprefixを付けた名前のパイプを使います。 switchはパラメータの名前で、この(パイプに格納された)パラメータの値が文字列expectと等しいとき復元を行います。
      xslt
      stylesheet
      要素に対し、stylesheet(XSLTファイル)による変換を行います。
      replace
      TODO

      Pull

      概要

      Pullは反復子として動作します。 ResultSetなどの反復型のデータソースの接続のほか、 単純に1つの値を返すためにも利用できます。

      作成方法

      Pull インターフェイスを実装したクラスを作成します。 あるいは、 AbstractPull を継承します。

      1つの値しか返さないのであれば、 AbstractSingleValuePull を継承するのが簡単です。 また、複数の値を返す場合には AbstractMultiValuePull を継承するのが簡単な場合があります。

      リファレンス

      now
      引数なし
      ISO8601形式で現在の時刻を返します。
      format
      java.text.SimpleDateFormatの形式で現在の時刻を返します。
      paginate

      大量のデータを複数のページに分割する表示方法を実現します。 結果としてoffset(開始位置)とpage(ページ番号)という二つの値を持つマップがリンクの数だけ返されます。

      以下のテンプレートを用いることで検索サイトなので使われるページ分割を実現できます。 offset、pageSize、pageLinks、countにはあらかじめ、 データ取り出し開始位置、1ページに表示する最大データ数、1ページに表示する各ページへのリンクの最大数、 データの総数が設定されているものとします。

      <cls-logic>
      .ページ {
        =paginate/pages: "${offset}" "${pageSize}" "${pageLinks}" "${count}";
        present: pages;
      }
      
      .ページ .選択 {
        iterate: pages i;
      }
      
      .ページ .移動 {
        not-equal: "${i:offset}" "${offset}";
      }
      
      .ページ .現在 {
        equal: "${i:offset}" "${offset}";
      }
      </cls-logic>
      <div cls:class="ページ">
        ページ:
        <span cls:class="選択">
          <a cls:class="移動" href="page.html?offset=${i:offset}">${i:page}</a>
          <span cls:class="現在">${i:page}</span>
        </span>
      </div>
      
      environment
      name
      CGI同様の環境変数を返します。 詳細は WebContext のgetEnvironmentを参照してください。

      Push

      概要

      データを押し込みます。 ページの生成と非同期に動作するのが特徴です。

      作成方法

      Push インターフェイスを実装したクラスを作成します。 あるいは、 AbstractPush を継承します。

      リファレンス

      現在のところ、標準コンポーネントとして用意されているPushはありません。

      Action

      概要

      Actionはページ生成と並行する必然性がない処理を行います。 主な目的は、スクリーン上のデータの加工、データベースの更新です。

      作成方法

      Action インターフェイスを実装したクラスを作成します。 あるいは、 AbstractAction を継承します。

      リファレンス

      default-to
      key s
      パイプkeyが空であれば、文字列sを設定します。
      expand
      key1 key2
      パイプkey1から値を取り出し、コレクションか配列であればパイプkey2に全ての要素を展開します。
      push
      commit-messages

      このActionの使用例、しくみについてはエラー処理とメッセージを参照してください。

      引数なし
      メッセージをコミットし、次のページへ1回だけ引き継ぎます。
      'true'|'false'
      メッセージをコミットします。 引数がtrueの場合は次のページに1回だけ引継ぎますが、falseの場合はそのページ限りとなります。
      reverse
      key
      パイプkeyの値を全て取り出し、逆順に追加します。
      forward
      uri
      現在のリクエストをuriに転送します。 uriを'/'で開始する場合は、コンテキストからの絶対パスとなり、そうでない場合は相対パスです。
      redirect
      uri
      uriにリダイレクトするよう、クライアントに送ります。
      use-attribute

      scopeは'request'|'session'|'application'で表されるスコープで、 それぞれSunのサーブレット仕様のリクエスト、セッション、アプリケーションスコープに対応します。

      name
      name scopeでスコープに'request'を使用するのと同じ意味になります。
      name scope
      name scope keyでkeyにnameお同じ値を渡すのと同じ意味になります。
      name scope key
      名前keyのパイプに値が存在する場合はパイプの値を指定されたスコープの名前nameの属性に設定します。 名前keyのパイプに値が存在せず、指定されたスコープに名前nameの属性が存在すれば、名前keyのパイプに追加します。 いずれでもない場合は、何もしません。
      remove-attribute

      scopeは'request'|'session'|'application'で表されるスコープで、 それぞれSunのサーブレット仕様のリクエスト、セッション、アプリケーションスコープに対応します。

      name
      リクエストスコープの名前nameの属性を削除します。
      name scope
      指定されたスコープの名前nameの属性を削除します。
      load-form

      scopeは'request'|'session'|'application'で表されるスコープで、 それぞれSunのサーブレット仕様のリクエスト、セッション、アプリケーションスコープに対応します。

      このActionの使用例、しくみについてはHTMLフォームを参照してください。

      type
      type scapeでスコープに'request'を使用するのと同じ意味になります。
      type scope
      type scope keyでkeyにtypeと同じ文字列を使用するのと同じ意味になります。
      type scope key
      type scope key prefixでprefixにkeyと同じ文字列を使用するのと同じ意味になります。
      type scope key prefix
      type scope key prefix 'validate'|'no-validation'で最後の引数を'validation'とするのと同じ意味になります。
      type scope key prefix 'validate'|'no-validation'
      名前keyのパイプにフォームが存在する場合はパイプの値を指定されたスコープの名前keyの属性に設定します。 名前keyのパイプにフォームが存在せず、指定されたスコープに名前keyの属性にフォームが存在すれば、名前keyのパイプに追加します。 いずれでもない場合は、typeで指定されるフォームが作成され、指定されたスコープの名前keyの属性と、名前keyのパイプに追加されます。
      その後、フォームに保持されている全てのパラメータについて、 パラメータ名の先頭にprefix+'.'という文字列を付けた名前のパイプに値を追加します。 最後の引数が'validate'であればバリデーションを行い、'no-validation'であればバリデーションをしません。
      save-form

      scopeは'request'|'session'|'application'で表されるスコープで、 それぞれSunのサーブレット仕様のリクエスト、セッション、アプリケーションスコープに対応します。

      このActionの使用例、しくみについてはHTMLフォームを参照してください。

      type
      type scapeでスコープに'request'を使用するのと同じ意味になります。
      type scope
      type scope keyでkeyにtypeと同じ文字列を使用するのと同じ意味になります。
      type scope key
      type scope key 'marshal'|'no-marshal'で最後の引数を'marshal'とするのと同じ意味になります。
      type scope key 'marshal'|'no-marshal'
      type scope key 'marshal'|'no-marshal' 'validate'|'no-validation'で最後の引数を'validate'とするのと同じ意味になります。
      type scope key 'marshal'|'no-marshal' 'validate'|'no-validation'
      名前keyのパイプにフォームが存在する場合はパイプの値を指定されたスコープの名前keyの属性に設定します。 名前keyのパイプにフォームが存在せず、指定されたスコープに名前keyの属性にフォームが存在すれば、名前keyのパイプに追加します。 いずれでもない場合は、typeで指定されるフォームが作成され、指定されたスコープの名前keyの属性と、名前keyのパイプに追加されます。
      その後、現在のページに渡されたリクエストパラメータをフォームに格納します。 最後から2番目の引数が'marshal'の場合は正規化を行い、'no-marshal'であれば正規化を行いません。 最後の引数が'validate'であればバリデーションを行い、'no-validation'であればバリデーションをしません。

      Module

      概要

      パイプ名に対応する出口を動的に作成します。

      作成方法

      Module インターフェイスを実装したクラスを作成します。 あるいは、 AbstractModule を継承します。

      リファレンス

      messages

      メッセージを次の名前でスクリーンから取り出せるようにします。 なお、(重要度)はinfo|invalid|errorのいずれかで、(カテゴリー)は開発者が決めることができます。 詳細はエラー処理とメッセージを参照してください。

      prefix(重要度).(カテゴリー)
      特定の重要度の特定のカテゴリーの全てのメッセージ
      prefix(重要度)
      特定の重要度の全てのメッセージ
      prefix
      全てのメッセージ
      parameters
      ['ordinal'|'random']
      prefix(リクエストパラメータ名)という名前でリクエストパラメータの値を取り出せるようにします。 ordinalを指定すると、RFC1866 8.2.1に従う順でしかパラメータにアクセスできなくなります。 randomを指定すると、順序に関係なくパラメータを取り出せます。 デフォルトはrandomです。
      i18n

      TODO

      Replacer

      概要

      Replacerは置き換えタグを実現するものです。

      Replacer インラーフェイスを実装することで、置き換えタグの形式は変更可能です。

      simple

      デフォルトの最も単純なReplacerでは、 以下の置き換えタグを利用できます。

      置き換えタグ意味
      ${キー} パイプの先頭のオブジェクトを文字列として出力します。
      ${キー:マップキー} パイプの先頭のオブジェクトがjava.util.Mapを実装していれば、マップキーを使った取り出した値を文字列として出力します。
      ${#キー} パイプから飛りだしたオブジェクトを文字列として出力します。
      ${#キー:マップキー} パイプから取り出したオブジェクトがjava.util.Mapを実装していれば、マップキーを使った取り出した値を文字列として出力します。
      $$ '$'を表示します。

      各種機能

      Mapper

      概要

      CLSテンプレートファイルから出力結果を生成するコンポーネントは Mapper という名前です。 これは、当初からサーブレットにアクセスするためのURIと、CLSテンプレートファイルをマッピングしていたことに由来します。

      使用例はinfo.port4.pasta.cls.filters.IncludeFilterが実際に使っているので、ソースを見るのがよいでしょう。

      Root Mapper

      Pastaの設定ファイル(pasta.xconf)にmapper-rootという項目があります。 これは、URIとCLSテンプレートファイルのマッピングを設定するものです。 Pastaサーブレットにブラウザでアクセスするときのパスとテンプレートファイルの関係、 あるいはinclude Filterに渡すURIと挿入されるテンプレートの関係はこの設定によります。

      pasta.xconfには以下の記述があります。

        <mapper-root id="root" default="true">
          <parameter name="config-file" value="context:/WEB-INF/mapping.xml"/>
        </mapper-root>
      

      これは、マッピングの詳細は/WEB-INF/mapping.xmlに記述されているということです。 このファイルの変更は実行中に検出されるので、mapping.xml修正後にサーブレットコンテナを再起動する必要はありません。

      mapping.xmlファイルの内容は、大抵次のようなものです。

      <?xml version="1.0" encoding="UTF-8"?>
      
      <mapping mime-type="text/html; charset=Shift_JIS"
               form-encoding="Shift_JIS">
      
        <mapper pattern="/jsp/*.html" map-to="/jsp/{1}.jsp"
                type="html-forward"/> 
        <mapper pattern="/**.html" map-to="context:/{1}.html"
                type="html-file"/>
        <mapper pattern="/**.xhtml" map-to="context:/{1}.xhtml"
                type="xml-file"/>
        <mapper pattern="/**.act" map-to="context:/{1}.act"
                mime-type=""
                type="xml-actfile"/>
        <mapper pattern="/**.jpg" map-to="context:/{1}.jpg"
                mime-type="image/jpeg"
                type="direct"/>
      
      </mapping>
      

      ルート要素mappingにはデフォルトのMIME型と、フォームのエンコーディングが記述されています。 これは、Pastaサーブレットが返すContent-Typeヘッダの値と、フォームから送られたパラメータをデコードする際のキャラクタ・エンコーディングです。 これらの設定は内部のmapper要素で上書きすることもできます。

      以下はmapper要素の各属性の説明です。

      pattern 外部からアクセスする時のURIパターンです。
      map-to 実際に使用するCLSテンプレートです。 先頭にcontext:とつけるのは、ウェブアプリケーションディレクトリ内のファイルを利用することを表しています。 ただし、type属性で指定するMapperのタイプによっては意味合いが変わることもあります。
      type Mapperのタイプです。
      mime-type Mapperで上書きするMIME型。省略するとデフォルト値が使われます。
      form-encoding Mapperで上書きするフォームのエンコーディング。省略するとデフォルト値が使われます。
      internal-only "true"を指定すると、プログラム内からのアクセスに限定し、ウェブブラウザなどで直接ページを表示できないようにします。 プログラム内からのアクセスとは、include Filterによるページの利用や、プログラム内から直接Mapperを呼び出す場合です。

      URIパターンの記述方法はCocoon2の WildcardMatcher 形式を採用しているので、こちらも参照してください。

      設定例についてはPasta サーブレットの説明も参照してください。

      サーブレットとPasta

      設定方法

      サーブレット版のPastaは PastaServlet という名前のサーブレットです。

      web.xmlには以下のように設定してください。

        <servlet>
          <servlet-name>pasta</servlet-name>
          <servlet-class>info.port4.pasta.servlet.PastaServlet</servlet-class>
          <init-param>
            <param-name>config-file</param-name>
            <param-value>Pasta設定ファイル(省略した場合"/WEB-INF/pasta.xconf")</param-value>
          </init-param>
          <init-param>
            <param-name>logger-file</param-name>
            <param-value>ログ設定ファイル(省略した場合"/WEB-INF/logger.xlog")</param-value>
          </init-param>
          <init-param>
            <param-name>bootstraps</param-name>
            <param-value>
              Bootstrapの実装クラス名をスペース文字で区切って記述する
              省略すると、以下の2つがロードされます。
              info.port4.pasta.bootstrap.BasicBootstrap
              info.port4.pasta.servlet.bootstrap.ServletBootstrap
            </param-value>
          </init-param>
          <load-on-startup>1</load-on-startup>
        </servlet>
      
      
        <servlet-mapping>
          <servlet-name>pasta</servlet-name>
          <url-pattern>*.html</url-pattern>
        </servlet-mapping>
        <servlet-mapping>
          <servlet-name>pasta</servlet-name>
          <url-pattern>*.act</url-pattern>
        </servlet-mapping>
      

      上の例ではサーブレットを、*.htmlと*.actにマッピングしていますが、他の拡張子を使っても構いません。

      Bootstrapについての説明はPastaの起動と拡張を参照してください。

      設定ファイル

      Pastaの設定ファイルは以下の3つです。 別の名前でも可能ですが、デフォルトの名前を以下に示します。

      pasta.xconf
      Excalibur Fortressの設定ファイルです。 このファイルの記述方法はPastaの起動と拡張を参照してください。
      logger.xlog
      Excalibur Logkitによるログの設定ファイルです。
      mapping.xml
      このファイルの位置はpasta.xconf内に記述されています。 URIのマッピングを設定するファイルです。

      典型的なmapping.xmlの記述方法

      mapping.xmlの詳細はMapperのドキュメントを参照してください。 ここでは、よくある設定方法を紹介しておきます。

      HTML/XHTMLの変換

      mapping.xmlに以下の設定をすることで、 PastaServletによってHTMLまたはXHTMLファイルを読み込み、変換してクライアントに送出できます。

        <mapper pattern="/*.html" map-to="context:/html/{1}.html" type="html-file"/>
      

      このマッピングは、アプリケーションURIの直下の.htmlで終わるリソースをブラウザが要求すると、 htmlディレクトリ直下の同じ名前のファイルを変換したものを返すことを表します。 次の設定も同様の働きをしますが、ファイルをXHTMLとして解析します。

        <mapper pattern="/*.html" map-to="context:/html/{1}.html" type="xml-file"/>
      

      アクションを実行する.actファイルは、以下のようにマッピングしておきます。

        <mapper pattern="/*.act" map-to="context:/actions/{1}.act" type="xml-actfile"/>
      

      これで、actionsディレクトリに置かれたアクションファイルが実行できるようになります。

      JSPの変換

      JSPの変換には、サーブレットの転送(forward)機能を利用し、転送先のページをキャプチャすることで実現しています。 ページのキャプチャと変換処理を並行して実現するために新しいスレッドを起動するので、1つのリクエストを処理するために2つのスレッドが必要です。

      例えば、.htmlで終わる名前のリソースを要求された場合、jspディレクトリ以下のJSPファイルを変換したものを返すにはmapping.xmlに以下の設定を加えます。

        <mapper pattern="/*.html" map-to="/jsp/{1}.jsp" type="html-forward"/>
      

      アクションと画面遷移

      DBを更新し、画面を移るには?

      ウェブアプリケーションでは、フォームからサーバーへデータを渡し、 データベースの更新などの処理を行ってから、次の画面に移るという処理の繰り返しが多くなります。

      サーブレットを使ってこの処理をひたすら書くのも1つの方法ですが、 CLSのプログラミング言語としての側面を利用することで、モジュール化が容易になります。

      cls-action要素をHTML中に記述すると、その要素内でスクリプティングが可能になります。 Pastaでは画面出力を行わない、スクリプティングのためだけのページを用意して、DB更新などの「アクション」を記述できます。

      記述方法

      HTMLとして記述することも可能ですが、プログラマが書くものなので、XMLを使いましょう。 アクションは大抵の場合次のようなXMLファイルとして記述します。

      <?xml version="1.0" encoding="Shift_JIS"?>
      
      <actions xmlns:cls="http://port4.info/2002/CLS">
      
        <cls:action>
          $messages;
          @-user-reply;
          [not-present: messages.invalid; @redirect: "next.html";]
          [present: messages.invalid; @forward: "error.html";]
        </cls:action>
        
      </actions>
      

      上の例ではルート要素はactionsとなっていますが、実際のところ、何でも構いません。 意味を持つのはCLSの名前空間宣言と、cls:action要素です。

      @-user-reply;はユーザーが定義したActionコンポーネントです。 先にmessages Moduleを起動していますが、このModuleは@-user-reply;が出力した エラーにmessages.で始まる名前のパイプからアクセスできるようにするものです。 messages.invalid(バリデーションエラー)というエラーが発生していなければnext.htmlにリダイレクトし、 エラーが発生していればerror.htmlに転送します。

      メッセージ

      概要

      ウェブページ上で何らかの操作を起こしたとき、その結果をメッセージとして表示することはユーザーに安心感を与えます。 特に、何らかの原因で操作が実行されなかった場合、あるいはエラーが発生した場合、それは必須となります。

      例として、入力フォームで記入漏れがあったためにその操作が実行されなかった場合、 エラーページを表示してエラーメッセージを表示し、ブラウザの「戻る」ボタンを使って戻ってもらうか、 もっと親切なアプリケーションであれば、再び入力フォームを表示して、そこにエラーメッセージを出すでしょう。

      メッセージ

      MessageHolder はユーザーに対してメッセージを表示するためのオブジェクトで、各種コンポーネントに必ず渡されます。 メッセージは次の2段階に分けられています。

      info
      「○○を更新しました。」のような通常のメッセージです。
      invalid
      「○○が制限×を超えています。」のような入力に対するエラーメッセージです。

      MessageHolderには、メッセージのカテゴリー、例外オブジェクトと共にメッセージ文を追加することができます。 カテゴリーは、後でメッセージを画面に表示する際に、メッセージの表示場所を決めるために使います。

      その他、特殊なメッセージとしてerrorメッセージがあります。 これは環境に起因する問題など、ユーザーの責任ではないエラーで、即座に画面に出力されます。

      例外オブジェクトはロギングのために使います。 errorメッセージに対する例外はWARN(警告)レベルで、その他の例外はDEBUG(デバッグ)レベルでログに出力されます。

      MessageHolderは独特のライフサイクルを持っています。 初期状態では何のメッセージも追加されていない状態です。 メッセージをいくつか追加すると、hasMessage,hasInvalid,hasErrorメソッドによってメッセージの存在が確認できるようになります。 しかし、その状態ではgetMessagesメソッドによってメッセージは取得できません。 追加されたメッセージは、commitメソッドを実行することで、getMessagesで取り出せるようになります。 このとき、逆にhasMessage,hasInvalidメソッドでメッセージの存在を確認できなくなります(常にfalse)が返ります。

      例えばhasInvalidは、以下のように現在の処理で1度でも不正な入力やエラーがあったかを判別するために使うことができます。

      	public void act(Screen screen, MessageHolder mh, Map objectModel, String[] args) {
      		//パラメータ取得
      		...
      		
      		//入力ミスのチェック
      		...
      		
      		//不正な入力があれば処理を中断
      		if (mh.hasInvalid()) {
      			return;
      		}
      	}
      

      MessageHolderのcommitを呼び出すために、 commit-messagesアクション が使うことができます。 また、messagesモジュール はgetMessagesによってメッセージを取り出すことができます。 これを、アクションと画面遷移の記述に利用すると便利です。

        メッセージを出力するような処理...
        @commit-messages;
        $messages;
        [
          not-present: messages.invalid;
          正常に処理を終えた場合...
        ]
      
        [
          present: messages.invalid;
          不正な入力があった場合...
        ]
        </cls:action>
      

      MessageHolderの表示可能なメッセージは、commitメソッドを呼び出した後、 1回だけ次のページに引き継ぐことができます。 上記のアクションを実行した後、次のテンプレートを実行すると、invalidレベルのメッセージが全て表示されます。

      <body cls:logic="$messages;">
        <ol cls:logic="present: messages.invalid">
          <li cls:logic="iterate: messages.invalid m;">${m}</li>
        </ol>
      </body>
      

      カテゴリーを使うと、さらにカテゴリーごとにメッセージの表示場所を制御することが出来ます。 例えば、名前フィールドに関するメッセージを"name"というカテゴリーにして、 eメールフィールドに関するメッセージを"email"というカテゴリーにすると、 それぞれの入力フィールドの上に、対応するメッセージを表示できます。

      <form cls:logic="$messages;">
        <div cls:logic="iterate: messages.invalid.name m;">${m}</div>
        名前:<input type="text" name="name" value=""/>
        <div cls:logic="iterate: messages.invalid.email m;">${m}</div>
        eメール:<input type="text" name="email" value=""/>
      </form>
      

      HTMLフォームのバリデーション・復元

      概要

      フォームの入力内容の正当性をチェックしたり、 以前のフォームを復元する処理はウェブアプリケーションのあらゆる場面で登場します。 Pastaは、そのための統一的なインターフェースを用意しています。

      Pastaはプログラミング言語の構造体(オブジェクト)とフォームを結びつける手法も用意しています。 このようなフレームワークと言えばStrutsが有名ですが、 Pastaのアプローチはさらに「現実的」です。

      まず、Pastaはリクエストパラメーターの値をオブジェクトに移す処理を自動では行いません。 全てプログラミングによって行います。 それは、経験上、現実の問題はこの処理を自動化できるほど単純ではないという考えによります。

      また、フォームとオブジェクトを1対1で対応させることはしません。 現実のアプリケーションではデザインや操作性を重視するために冗長なフォームを作ることがあります。 このとき、オブジェクトにデータを移す課程でデータが欠落することがあります。 そのため、プログラマは冗長なフォームに合わせてオブジェクトも冗長化するという本末転倒なことをしなければならなくなります。

      一方でPastaはリクエストパラメーターとフォームを自動的に結び付けます。 そして、そのためにプログラマが用意したオブジェクトとは別にリクエストパラメーターをキャプチャしたものを保存します。 つまり、Pastaはフォームから送られたデータを2重に保持します。 これはまさに「冗長なオブジェクトを使う」ということなのですが、実際に冗長なフォームがある以上現実的な方法です。 ただ、Strutsとの違いは、冗長な部分の管理はプログラマではなくPastaが勝手にやってくれるところです。

      しかし、心配することはありません。フォームでやりとりされる情報量はたかが知れています。 1000バイトの情報を保持するのも、2000バイトの情報を保持するのも、サーバーマシンにとっては変わらないでしょう。 また、さすがにアップロードされたファイルをそのまま保持することはありません。 ファイルアップロードについては別のしくみが用意されています。

      フォームの復元

      HTMLのフォームから送られるリクエストは、1つの名前に対して複数の値が対応する名前付きパラメータです。 また、パラメータの名前と値の組み合わせはドキュメントにそのパラメータを送り出す要素(input,select,textarea) が現れた順になります(RFC1866 8.2.1)。

      Pastaには、スクリーンからリクエストパラメータにアクセスするためのモジュール parameters が用意されています。 このモジュールにより、"parameters.パラメータ名"という名前で、パラメータ名に対応する値をパイプから取得できます。

      そして、リクエストパラメータからフォームを復元するロジックが restore です。 このロジックは、request-parametersモジュールによってスクリーンからパラメータを取得できるようになった状態で、フォームの部品ごとに以下の手順で復元します。

      text,password,hidden,submitフィールド

      restoreロジックが、フォーム内でこれらのフィールドを見つけると、input要素の属性nameに対応するパラメータが存在するか調べます。 パラメータが存在した場合は、パラメータをパイプから取り出し、input要素のvalue属性に設定します。 パラメータが存在しない場合は何もしません。

      textareaフィールド

      restoreロジックが、フォーム内でこのフィールドを見つけると、textarea要素の属性nameに対応するパラメータが存在するか調べます。 パラメータが存在した場合は、パラメータをパイプから取り出し、textareaの内容に設定します。 パラメータが存在しない場合は何もしません。

      radio,checkboxフィールド

      restoreロジックが、フォーム内でこのフィールドを見つけると、input要素の属性nameに対応するパラメータが存在するか調べます。 パラメータが存在し、なおかつその値がvalue属性に一致する場合だけ値を取り出し、input要素にchecked属性を付加します。 パラメータが存在しない場合は何もしません。

      select(multiple属性なし)フィールド

      restoreロジックが、フォーム内でこのフィールドを見つけると、select要素の属性nameに対応するパラメータが存在するか調べます。 パラメータが存在した場合、内部のoption要素を先頭から調べ、value属性がパラメータの値と一致するものが見つかれば値を取り出し、option要素にselected属性を付加し、そのselect要素の処理を終えます。 パラメータが存在しない場合は何もしません。

      select(multiple属性あり)フィールド

      restoreロジックが、フォーム内でこのフィールドを見つけると、select要素の属性nameに対応するパラメータが存在するか調べます。 パラメータが存在した場合、内部のoption要素を先頭から調べ、value属性がパラメータの値と一致するものが見つかれば値を取り出し、option要素にselected属性を付加しします。 これを、select要素の最後まで続けます。 パラメータが存在しない場合は何もしません。

      以上のアルゴリズムには制約があります。 例えば、次の場合には正確にフォームを再現できません。

      • name属性とvalue属性の値が全く同じラジオボタンまたはチェックボックスが同一フォームに複数存在する場合。
      • value属性の値が全く同じoption要素が同一セレクトボックスに複数存在する場合。

      ただし、それぞれのフィールドの意味合いから考えて、このようなフォームが必要になることは実用上まずないでしょう。

      以下は、自分自身のページに送られたパラメータでフォームを復元するという単純なテンプレートです。

      <html>
          <head><title>FORM</title></head>
        <cls-logic>
          .restore {
            $parameters;
            |restore;
          }
        </cls-logic>
        <body>
          <form cls-class="restore">
            <div>
              苗字:<input type="text" name="familyName" value="">
            </div>
            <div>
              名前:<input type="text" name="givenName" value="">
            </div>
            <button type="submit">送信</button>
          </form>
        </body>
      </html>
      

      セッションフォーム

      フォームを保持するためのクラスとして、 Form が用意されています。 これは、単純にリクエストパラメータを表現する WebParameters のインスタンスを内包するものです。 一方、このフォームを生成するためのインターフェースが FormFactory です。アプリケーション開発者が新しいフォームを作成する場合はFormFactoryを実装することになります。 開発者はFormにあるパラメータの「初期化」「正規化」「バリデーション」「再パラメータ化」の4つの操作をするための抽象メソッドを実装する必要があります。 以下に例を示します。

      public class NameFormFactory implements FormFactory {
          public Form createForm() {
              return new NameForm();
          }
          
          //名前、苗字を保持するクラス
          public static class Name {
              public String familyName;
      
              public String givenName;
          }
      
          public static class NameForm extends Form {
              //初期状態
              protected Object initialize() {
                  Name form = new Name();
                  form.givenName = "ごんべえ";
                  return form;
              }
      
              //正規化
              protected Object marshal(WebParameters par) {
                  Name form = new Name();
                  form.familyName = StringUtils.trimToNull(par
                          .getParameter("familyName"));
                  form.givenName = StringUtils.trimToNull(par
                          .getParameter("givenName"));
                  return form;
              }
      
              //バリデーション
              protected Object validate(Object _form, MessageHolder mh) {
                  Name form = (Name) _form;
                  if (form.familyName == null) {
                      mh.invalid("familyName", "苗字は必須です。");
                  } else if (form.familyName.length() > 20) {
                      mh.invalid("familyName", "苗字は20文字以内です。");
                  }
                  if (form.givenName == null) {
                      mh.invalid("givenName", "名前は必須です。");
                  } else if (form.givenName.length() > 20) {
                      mh.invalid("givenName", "名前は20文字以内です。");
                  }
                  return form;
              }
      
              //再パラメータ化
              protected WebParameters reform(Object _form) {
                  Name form = (Name) _form;
                  DefaultWebParameters par = new DefaultWebParameters();
                  par.addParameter("familyName", form.familyName);
                  par.addParameter("givenName", form.givenName);
                  return par;
              }
          }
      }
      

      上記のプログラムは、省略不可能で、かつ20文字以内の苗字と名前を入力するためのフォームを作成します。 初期状態では、苗字に「ごんべえ」が入っています。 このフォームにパラメータが入力されると、まずmarshal(正規化)メソッドにより、パラメータをNameFormクラスに変換します。 そして、validate(バリデーション)メソッドにより正しく入力されているかを検証します (ここでのMessageHolderの扱いについてはメッセージ のドキュメントを参照してください)。 そして、reform(再パラメータ化)メソッドによりNameFormをパラメータに戻します。

      initializeメソッドの役割は、最初の入力状態を構築することです。

      marshalメソッドの役割は、パラメータをJavaオブジェクトに変換し、扱いやすくすることです。 また、この際に値のトリムをしたり、全角数字を半角にするなど、「気の利いた」変換をします。

      validateメソッドは、正規化されたフォームが、データとして正当なものであるかを検証するものです。 MessageHandlerにinvalidメッセージが追加されていれば、不正なデータということになります。

      reformは、正規化されたデータを再びパラメータに戻し、フォームを復元するためのものです。 marshalメソッドが何らかの気の利いた変換をしている場合は、復元されたフォームが、 ユーザーが以前に入力したとおりになるとは限りません。

      この例ではName,NameFormはstatic inner classとなっていますが、別のクラスに分けても構いません(static inner classを忘れた方はJavaの言語使用を調べましょう...)。 また、実際にプログラマが必要な情報を保持するNameクラスは普通のクラスです。JavaBeansではありません。 また、何にも依存していません。つまり、既存のクラスでもなんでも利用できるということです。

      テンプレート上でFormFactoryを利用するには、 load-form Action を使ってください。

      例えば、NameFormFactoryを-user-name-formという名前で登録してあるとします。

      <html>
        <head>
          <cls-logic>
            .form {
              $messages;
              @load-form: -sample-name session;
              @commit-messages;
              |restore: -sample-name;
            }
            .invalidFamilyName {
              iterate: messages.invalid.familyName m;
            }
            .invalidGivenName {
              iterate: messages.invalid.givenName m;
            }
          </cls-logic>
          <title>FORM</title>
        </head>
        <body>
          <form cls-class="form" action="set-name.act">
            <div>
              <div cls-class="invalidFamilyName" style="color: red;">${m}</div>
              苗字:<input type="text" name="familyName" size="40" value="">
            </div>
            <div>
              <div cls-class="invalidGivenName" style="color: red;">${m}</div>
              名前:<input type="text" name="givenName" size="40" value="">
            </div>
            <button type="submit">送信</button>
          </form>
        </body>
      </html>
      

      このフォームから送られたパラメータをNameおよびNameFormオブジェクトに格納するには、 save-form Action を使います。

      <?xml version="1.0" encoding="Shift_JIS"?>
      
      <cls:span xmlns:cls="http://port4.info/2002/CLS">
      
        <cls:action>
        
          $messages;
          $parameters;
          @save-form: -sample-name session;
          @commit-messages;
          
          [ not-present: messages.invalid;
          @redirect: 'index.html'; ]
          [ present: messages.invalid;
          @forward: 'sessionform.html'; ]
          
        </cls:action>
      
      </cls:span>
      

      ファイルのアップロード

      概要

      Javaのウェブアプリケーションでファイルアップロードを扱うことは、初心者にとって1つの難関となります。 なぜならServletには、このリクエストを統一して扱うためのインターフェースが用意されていないためです。 大抵の場合、ServletRequestのgetParameterメソッドを使ってパラメータを文字列として取り出すことすらできません。

      HTMLにファイルアップロードのためのフォームを用意するには、 form要素のenctype属性に"multipart/form-data"を指定します。 以下は、ファイルアップロードフォームの例です。 (labelやinput要素を始めて目にする方は とほほのWWW入門 で勉強してください。)

        <form action="upload.act" method="post" enctype="multipart/form-data">
          <label for="file">アップロードするファイル:</label>
          <input type="file" name="file" id="file">
        </form>
      

      このとき、ブラウザはeメールによく似た形式でファイルとリクエストパラメータを送信します。 このデータは普通のリクエストパラメータと全く形式が異なるばかりか、非常に大きなサイズを扱うので、特別な処理を必要とします。 Pastaは、そのためのインターフェースを用意しています。

      プログラムの書き方

      WebContextのgetParameterメソッドは、 通常のリクエストとmultipart/form-dataリクエストの両方でパラメータが得られることを保証しています。 また、ファイルアップロードはprocessUploadメソッドによって処理します。 ここで注意しなければならないのは、processUploadはgetParameterあるいはgetParameterValuesによってパラメータが取り出される前に呼び出されなければならないことです。 なぜなら、パラメータの解析はgetParameter,getParameterValues,processUploadのいずれかが最初に呼ばれた時点で一括して行われ、 パラメータは保持されますが、ファイルはメモリを圧迫するのを避けるために即座に破棄されるためです。 ファイルをディスクに保管したい場合は、パラメータを取得する前にprocessUploadを呼んで、データをディスクに書き込むプログラムを書く必要があります。

      以下は、ファイルアップロードを処理するコードの例です。

        webContext.processUpload(new UploadHandler() {
          public OutputStream handleFile(String fieldName, String contentType, String fileName) throws IOException {
            File file = new File(fileName);
            return new BufferedOutputStream(new FileOutputStream(file));
          }
        });
      

      これで、ユーザーがアップロードしたファイルがディスクに保存されます。 もちろん、実際はファイル名の重複を防ぐような処理が必要になるでしょう。 これらの処理を自動的にせずに、ユーザーに任せている理由は、全てのユーザーがアップロードされたファイルをディスクに保存するとは限らないためです。 例えば、データベースのBLOB型として保存することもあるでしょう。 その場合、一時的にディスクに保存する処理は、サーバーにとって余計な負担となります。

      国際化機能

      TODO

      メッセージの国際化はI18NMessageModuleを使うこと。 パラメータを使う場合はinfoでパラメタを出して${message.info.foo}という置き換えをすればOK。

      RootMapperでファイルの選択が可能。 また、objectModelにRootMapper.I18N_KEYでLocaleのリストを置ける

      開発者ガイド

      その他

      license

      Copyright 2004 MIYABE Tatsuhiko
      
         Licensed under the Apache License, Version 2.0 (the "License");
         you may not use this file except in compliance with the License.
         You may obtain a copy of the License at
      
             http://www.apache.org/licenses/LICENSE-2.0
      
         Unless required by applicable law or agreed to in writing, software
         distributed under the License is distributed on an "AS IS" BASIS,
         WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         See the License for the specific language governing permissions and
         limitations under the License.