Cocoon2
概要
CocoonはXMLをpublishするためのフレームワークです。Cocoonを用いることでXMLベースの アプリケーションの開発を行うことができます。このサイトはForrestで構築されていますが、 Forrestの裏ではCocoon2が働いています。ここではCocoon2の紹介および チュートリアルを行います。
Cocoon2とは
Cocoon1はXML(およびXSP)とXSLTを用いて動的に新しいXMLを生成するサーブレットを提供しました。 この技術をWebアプリに適用した場合XMLはコンテンツ、XSLTはプレゼンテーションロジックが 記述されます。Cocoon1はこのようにしてコンテンツとプレゼンテーションロジックの分離という 問題に対する一つの解を与えました。つまりCocoon1はVの部分の技術に焦点を当てていたわけです。
ところが最近のようにWebアプリケーションが大規模化してくると、分離したことによって 開発者がページ遷移を把握することが困難になってきました。
そこでCocoon2で新たに追加された機能がsitemapです。sitemapではリクエストに応じて どのようにコンポーネントを呼ぶかを記述します。つまり、Cocoon2ではsitemapという技術 を追加することによってCの部分の強化したということができます。
何ができるのか?
Cocoon2で何ができるのか、それはCocoon2を利用しているプロジェクトを見ることで 理解できます。
一番身近な例はこの文書自体です。ForrestはXMLからHTMLおよびPDFの変換にCocoon2を 利用しています。(この変換だけではないですが。。。)
Forrestの機能としてWebアプリとして生成することができます。Webアプリとして生成すると あとからどんどんXMLを追加することによって、リクエスト時にHTMLやPDFを作成することができます。
http://.../xxx.htmlのときはhtml生成用のxslt、http://.../xxx.pdfのときは pdf生成用のxsltを同一xmlに適用するといった、リクエストに応じた、単一コンテンツからの動的な 生成が可能となります。
pipeline
pipelineはCocoon2の中心となる概念です。Cocoon2ではpipelineがリクエストを受け、 pipeの中にSAXイベントを流し、レスポンスとして返します。
Cocoon2ではpipeline中にコンポーネントを配置していくことによって開発を進めていきます。 主要なコンポーネントとして
- Generator
- Transformer
- Serializer
があります。それぞれ以下の仕事を行います。
| コンポーネント名 | 仕事 |
|---|---|
| Generator | SAXイベントを生成する。生成のタネとしてはXMLやXSP,HTMLをはじめ画像ファイルやHTTP Requestなどもとることができる。 |
| Transformer | Generatorで生成されたSAXイベントを変換する。変換規則としては主にXSLを用いる。 |
| Serializer | SAXイベントをシリアライズする。XML、XHTML、PDFのような文書や、 JPEGやPNGなどの画像ファイルにシリアライズする。 |

その他のコンポーネントとしては以下のものがあります。
| コンポーネント名 | 仕事 |
|---|---|
| Reader | 画像ファイルやJSPなどをそのまま読み出す。 |
| Selector | SAXイベントを分岐させる。 |
| Matcher | マッチしたパイプラインを開始する。 |
| Action | 開発者が作成したアクションを呼び出す。 |
Sitemap
コンポーネントやpipelineなどCocoon2を構成する要素の定義を行います。 これらの定義はsitemap.xmapというファイルに記述します。 Strutsにおけるstruts-config.xmlに相当します。
SitemapはMVCモデルでいうところのコントローラのための情報を持ちます。 pipelineにはどのコンポーネントを使うか、リクエストに対してどのpipelineを使うか などの情報を記述します。
詳しい説明は省きますが、使用するAction(StrutsのActionと同様なJavaクラス)の 定義も行います。Actionが書けるので、Strutsなみに動的なWebアプリケーションを 開発することができます。
Cocoon2の周辺技術
XSLT
XSLT(eXtensible Stylesheet Language Transformations)は、 XMLを他の形式に変換するための言語です。代表的な用途としては、 XML→XHTMLのようなスタイルシート、XML→XMLのようなデータの加工があ げられます。Cocoon2では用意したXMLをどのように加工し、publishす るためにどのように整形するか、ということを定義するためにXSLTを用い ます。XSLTの記述はCocoon2を用いた開発において大部分を占めます。
XSP
XSP(eXtensible Server Pages)はXMLに対するJSP(Java Server Pages)と考えると分かりやすいかもしれません。しかし、JSPとは利用方 法に大きな違いがあります。JSPはJSPスクリプトレットにデータが埋めこまれた 時点でページとなります。一方XSPはデータが埋めこまれた時点ではまだ XMLです。つまりページではなくデータです。データの埋めこまれたXML にさらにXSLTをかけることによってようやくページとなります。

XSL-FO
FO(Formatting Objects)はXMLで文書のスタイルを記述するための仕様です。 Apache FOPはFOからPDF文書を作成するJavaライブラリです。XMLに対してXSLT を適用してFOを作成し、FOPにかけることでPDFにすることができます。XSL-FOを 利用することにより、XMLデータをPDF形式に加工するという選択肢が生まれます。 これはForrestでも採用されている方法でCocoon2を用いる場合の利点となってい ます。一つのXMLデータからHTMLとPDFにできるため、計算機での閲覧にはHTML、 印刷するためにはPDFといった利用形態が可能となりました。(つまりこのチュートリアルの形態です)

Cocoon2を用いた開発のチュートリアル
Cocoon2を使って簡単なサイトを構築してみましょう。例として単一XMLから リクエストによって見え方の異なるページを生成するページを作ります。
pipelineの追加
まずpipelineを追加します。sitemap.xmapに以下を追加します。
<map:match pattern="sample1">
<map:generate src="sample.xml"/>
<map:transform src="sample1.xsl"/>
<map:serialize type="html"/>
</map:match>
http://.../.../sample1というリクエストに対するpipelineを定義しています。 http://.../.../sample1というリクエストに対してsample.xmlから SAXイベントを生成し、sample1.xslで変換し、htmlとしてシリアライズし、 レスポンスとして返します。
コンテンツの追加
コンテンツを用意しましょう。以下のようなXMLをsample.xmlというファイル名で保存しましょう。
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<labs>
<lab id="1">
<name>阿草研</name>
<university>名古屋大学</university>
<url>http://www.agusa.i.is.nagoya-u.ac.jp</url>
<room>名古屋大学IB南館5F 551,559,561</room>
</lab>
<lab id="2">
<name>山本研</name>
<university>愛知県立大学</university>
<url>http://www.aichi-pu.ac.jp/ist/lab/yamamoto/index.html</url>
<room>愛知県立大学 C棟4F C408、C407</room>
</lab>
</labs>
</sample>
変換ルールの準備
XSLTを用意しましょう。以下のようなXSLをsample1.xslというファイル名で保存 しましょう。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:template match="sample">
<html>
<head>
<title>研究室一覧</title>
</head>
<body>
<xsl:apply-templates select="labs"/>
</body>
</html>
</xsl:template>
<xsl:template match="labs">
<table border="1">
<xsl:for-each select="lab">
<p>
<xsl:value-of select="name"/>は
<xsl:value-of select="university"/>の研究室です。
詳しくは
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="url"/>
</xsl:attribute>
<xsl:value-of select="url"/>
</xsl:element>
を参照してください。
</p>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
以上の手順でページを作成することができます。
違う見せ方で
それでは、同じコンテンツを違うデザインで表示してみましょう。今度は一覧ページ→詳細ページ といった感じで画面遷移するようにしてみます。ただし、データの発生源であるXMLは一つです。 まずpipelineを追加します。 sitemap.xmapに以下のように追加します。
<map:match pattern="sample2">
<map:generate src="sample.xml"/>
<map:transform src="sample2.xsl"/>
<map:serialize type="html"/>
</map:match>
sample.xmlは同じであることに注意しましょう。
変換ルールの追加
まず一覧用の変換ルールを用意します。以下のXSLをsapmle2.xslという名前で保存します。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:template match="sample">
<html>
<head>
<title>研究室一覧</title>
</head>
<body>
<xsl:apply-templates select="labs"/>
</body>
</html>
</xsl:template>
<xsl:template match="labs">
<p>研究室一覧</p>
<table border="1">
<xsl:for-each select="lab">
<tr>
<td><xsl:value-of select="name"/></td>
<td>
<xsl:element name="a">
<xsl:attribute name="href">
sample3?id=<xsl:value-of select="@id"/>
</xsl:attribute>
詳細はこちら
</xsl:element>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
もう一つ詳細ページ用の変換ルールを定義します。 以下のXSLをsample3.xslという名前で保存します。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:param name="labid"/>
<xsl:template match="sample">
<html>
<head>
<title>研究室詳細</title>
</head>
<body>
<xsl:apply-templates select="labs"/>
</body>
</html>
</xsl:template>
<xsl:template match="labs">
<xsl:for-each select="lab">
<xsl:if test="self::node()[@id=$labid]">
<h3><xsl:value-of select="name"/></h3>
<table border="1">
<tr><td>大学</td><td><xsl:value-of select="university"/></td></tr>
<tr><td>URL</td><td><xsl:value-of select="url"/></td></tr>
<tr><td>部屋</td><td><xsl:value-of select="room"/></td></tr>
</table>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
sample3.xslではlabidという変数を使っており値は外部から与えてやる必要があります。 これはsitemapと通して与えることができます。
この変換ルールに対するpipelineを追加します。
<map:match pattern="sample3">
<map:act type="request">
<map:parameter name="parameters" value="true"/>
<map:generate src="sample.xml"/>
<map:transform src="sample3.xsl">
<map:parameter name="use-request-parameters" value="true" />
<map:parameter name="labid" value="{id}"/>
</map:transform>
</map:act>
<map:serialize type="html"/>
</map:match>
このようにすることでhttp://.../.../sample3?id=xxx というリクエストに 対してxxxの値をsample3.xslのlabidという変数に渡すことができます。 このようにpipelineにはパラメータ等の値のコントロールも記述します。
ブラウザhttp://.../.../sample2にアクセスし、研究室を選んでまみましょう。 先程と違うデザインで同じコンテンツを表示することができました。また、画面遷移を 実現することができました。
以上で見たように、XMLとXSLTに分けることでコンテンツと プレゼンテーションロジックの分離が実現できました。
FlowScript
Cocoon2は様々な付加機能として機能が登載されています。ここではCocoon2の機能として FlowScriptを 紹介します。
FlowScriptは継続ベースのWebアプリケーションを実現する技術です。ページ遷移および データをJavaScriptで記述することによりセッション変数の使用を一箇所に集めることができ、 セッション変数の扱いが楽になります。
ここではセッション変数を使った簡単なサンプルを作成します。 以下に示すような簡単なログインアプリを作成します。

ソースを見る前にまず全体像を見てみましょう。

FlowScriptの実装
まず、使用するFlowScriptの定義を行います。sitemap.xmapに以下のように加えます。
<map:flow language="javascript">
<map:script src="sample.js"/>
</map:flow>
FlowScriptの準備
以下のJavaScriptコードをsample.jsというファイル名で保存します。
function login() {
var name = "";
var password = "";
/* passwordが一致するまでループ */
while (true) {
cocoon.sendPageAndWait("login.jx", { "name" : name , "password" : password } );
name = cocoon.request.get("name");
password = cocoon.request.get("password");
if (password == "test") {
break;
}
}
cocoon.sendPageAndWait("success.jx", {} );
var greeting = cocoon.request.get("greeting");
cocoon.sendPage("greeting.jx", { "name" : name, "greeting" : greeting});
}
cocoon.sendPageAndWait()で継続とともにクライアントにレスポンスを送り、 クライアントから継続を受けることでその行から再開します。
継続の開始など、pipelineの追加
継続の開始sitemap.xmapに以下のように加えます。
<map:match pattern="login">
<map:call function="login"/>
</map:match>
<!-- use JXtemplate to generate page content -->
<map:match pattern="*.jx">
<map:generate type="jx" src="{1}.jx"/>
<map:serialize type="xhtml"/>
</map:match>
<!-- .kont URLs are generated by the Flow system for continuations -->
<map:match pattern="*.kont">
<map:call continuation="{1}"/>
</map:match>
これでリクエストでloginを指定することでJavaScriptからlogin()が呼ばれ、継続が 開始します。また、continuation属性でFlowScriptに渡す継続を指定します。 ここではページの生成にJXテンプレートを利用します。JXテンプレートについては 詳しく説明しませんが、サンプルを見て理解できると思います。
Jexlテンプレートの用意
Jexlテンプレートでページの雛形を用意します。
login.jx
<?xml version="1.0" encoding="Shift_JIS"?>
<html xmlns:jx="http://apache.org/cocoon/templates/jx/1.0">
<head>
<title>Login Page</title>
</head>
<body>
<h1>Login Page</h1>
<p>name = ${name}</p>
<p>password = ${password}</p>
<form method="post" action="${cocoon.continuation.id}.kont">
<table>
<tr><td>Name:</td><td><input type="text" name="name"/></td></tr>
<tr><td>Password:</td><td><input type="password" name="password"/></td></tr>
<tr><td></td><td><input type="submit"/></td></tr>
</table>
</form>
</body>
</html>
success.jx
<?xml version="1.0" encoding="Shift_JIS"?>
<html xmlns:jx="http://apache.org/cocoon/templates/jx/1.0">
<head>
<title>cocoon flow number guessing game</title>
</head>
<body>
<h1>あいさつの言葉をどうぞ</h1>
<form method="post" action="${cocoon.continuation.id}.kont">
<table>
<tr><td>言葉:</td><td><input type="text" name="greeting"/></td></tr>
<tr><td></td><td><input type="submit"/></td></tr>
</table>
</form>
</body>
</html>
greeting.jx
<?xml version="1.0" encoding="Shift_JIS"?>
<html xmlns:jx="http://apache.org/cocoon/templates/jx/1.0">
<head>
<title>Greeting Page</title>
</head>
<body>
<h1>${greeting}! ${name}</h1>
</body>
</html>
以上がそろったら実際にloginにアクセスしてみましょう。最初のページで入力した値がページを 飛び越えてgreeting.jxで表示されます。以上でみたように、セッション変数をJavaScriptで 管理することができます。
おわりに
今回はStruts以外のWebアプリケーション開発のためのフレームワークの例としてCocoon2を 紹介しました。Cocoon2には今回紹介した機能の他にまだまだ便利な機能が組み込まれています。 例えばActionとしてさまざまなコンポーネントが提供されており、pipelineに組み込み設定 ファイルを用意することですぐに使えるものがたくさんあります。
CocoonはXMLを使用するWebアプリだけでなく、XSP(esql)を使うことでDBとの連携もスムーズに 行うことができます。
このように、Cocoon2には紹介しきれない機能がまだまだたくさんあります。興味があれば Cocoonのサイトを訪れて調べてみてください。今回のチュートリアルでCocoon2に興味を持って もらえたら幸いです。
by Ryo Suetsugu


