Powered by SmartDoc

Velocity活用術

竹本 浩
http://www.pwv.co.jp
Jakarta CommonsのVelocityとStrutsでVelocityを使うためのVelocity-Toolを使うためのテクニックを説明する。

目次

Last modified: Mon Jun 13 12:12:27 JST 2005

Velocity-Toolは、EStoreのビュー編

このレポートは、ショッピングカートの実装を通じてオープンソースツールをどのように活用するかを検証するレポートのビューレンダリング編である。

1 Velocityを使いこなす

JakartaプロジェクトのVelocityは、テンプレートツールとして有名であるが、strutsのビューワーとして使用する場合の利点についてはあまり知られていない。strutsがMVCを採用していると紹介する記事は多いが、どのようにしてビューの作成を他の処理と並行して開発するかについて言及したものは、少ない。ここでは、Eclipseのプラグインsimteecを使いながら、独立してビューの設計をする方法を紹介し、最後にpnutsを使ったテンプレート展開する手法を示す。

1.1 なぜVelocityを使うのか

strutsのビューとしては、strutsタグライブラリ、標準タグライブラリがよく使われるが、Velocity-Toolを使った方式については、まだ普及しているとは言えない。ここでは、デザイナーの視点に立ってビュー・レンダリングについて考えると、

が挙げられる。しかしながら、タグを使った制御やレンダリング処理ではWYSWYGのエディタでビューを設計することは難しい。

1.1.1 Velocityの特徴

Velocityの特徴を挙げると、

がある。例としてVelocity-Toolの例題edit-address.vmをブラウザで表示した図を以下に示す。(2)

図 1.1.1.1 edit-address.vmのプレビュー
  1. VTLの構文やフィールドの変数参照がそのまま表示される
  2. ファイル名をedit-address.htmlに変え、スペースの関係からコピーライトを削除した状態ファイルをプレビューした。

1.1.2 VTLの構文

Velocityテンプレートで使用されているテンプレート言語(以下VTLと記す)の構文は、非常にシンプルである。詳しくは、VTLのページを参照されたい。VTLを簡単に説明すると

変数アクセス

変数のアクセスには、

の3通りがある。(3)

属性(プロパティ)アクセス
属性へのアクセスは、ドット.の後に属性名を指定する。変数もしくは、直前の属性がハッシュマップの場合には、キーを属性として指定することでキーの値にアクセスできる。
メソッド呼び出し
$変数名.メソッド名()または$変数名.メソッド名(パラメータ)の形式でメソッド呼び出しが実行される
代入指示子

$set($lref = rref)の形式で指定する。

例:$set($money="123")

条件分岐指示子
の形式で指定し、conditionには、条件を指定し、outputには条件に一致したときに出力する文字列または、VTLを指定する。
#if (condition)
	output
#elseif (condition)
	output
#else
	output
#end
					
ループ指示子
の形式で指定し、
#foreach($ref in $list)
	statement
#end
					
インクルード指示子
#include(テンプレートファイル名)の形式でインクルードファイルを指定する。インクルードファイルのVTLは解析されないことに注意されたい。インクルードファイルのVTLを解析する場合には、#includeの代わりに#parseを使用する。
停止指示子
#stopでテンプレートの展開を停止する
マクロ定義指示子
#macro($vmname $arg1, $arg2, ... $argn)
	VTL code
#end											
					

の形式で指定する。

マクロの参照は、#vmname($arg1, $arg2, ... $argn)の形式で行う。

コメント
##行内コメントまたは、#*複数行コメント*#の形式で指定する
  1. shの変数アクセスに類似している

1.2 Eclipseのプラグインsimteec

Velocityテンプレートを直接使う代わりにEclipseのプラグインであるsimteecを使って、Velocityテンプレートを展開してみる。(4)

simteecの特徴は、

である。特にjavaのオブジェクトを変数にセットすることができるため、strutsのフォームにセットされるオブジェクトをそのまま使うことができる。

  1. simteecのインストールと設定方法については、こちらを参照されたい。

1.2.1 simteecを使って見る

簡単な例を使って、Velocityを使った時のテンプレート展開をプレビューしてみよう。例に使用するのは、Velocityとstrutsとの連携で使用されるVelocity-Toolの例題で、ログイン画面の例である。strutsでは、フォームの属性を使ってコントローラと情報を交換しており、例題でも$!logonForm.username、$!logonForm.passwordでフォームlogonFormにアクセスしている。

リスト 1.2.1.1 logon.vm
#*
 * Copyright 2003-2004 The Apache Software Foundation.
 *
 * 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.
 *
 * $Id: Velocity.html,v 1.6 2005/06/13 03:12:58 take Exp $
 *#
<html>
    <head>
        <title>Sign in, Please!</title>
        <base href="$link.baseRef">
    </head>

    <body>

        #errorMarkup()

        <h3>Sign in, Please! (Velocity Version)</h3>

        <form method="POST" action="$link.setAction('logonSubmit_vm')">

        <table border="0">
            <tr>
                <th align="right">
                    Username:
                </th>
                <td align="left">
                    <input type="text" name="username" value="$!logonForm.username">
                </td>
            </tr>

            <tr>
                <th align="right">
                    Password:
                </th>
                <td align="left">
                    <input type="password" name="password" value="$!logonForm.password">
                </td>
            </tr>

            <tr>
                <td align="right">
                    <input type="submit" value="Submit" name="submit">
                </td>
                <td align="left">
                    <input type="reset" value="Reset" name="reset">
                </td>
            </tr>

        </table>

        </form>

        <a href="$link.setForward("logon_vm_src")">View Template</a><br>

    </body>

</html>

simteecでは、変数の種類として変数、リスト、ハッシュマップの3種類を提供している。ハッシュマップでは、

	$変数名.マップキー
			

の様にマップのキーを属性のように指定してキーのバリューにアクセスすることができる。そこで、logonという名前のハッシュマップを定義し、username, passwordを登録することでlogonFormの代用とする。simteecのプロパティファイルを以下に示す。

リスト 1.2.1.2 logon.prop
## simteecコンフィグの設定
logonForm   = %(
	"username" => "Hiroshi TAKEMOTO",
	"password" => "****"
	);

simteec_output   = "logon.html";
simteec_overwrite="true";
simteec_template = "PROJECT:/vm/logon.vm";

simteecでのテンプレートの展開はパッケージエクスプローラで該当するプロパティファイルを選択し、右クリックで「simteec」→「generate」を選択するだけである。

展開されたHTMLファイルをブラウザーで表示すると、

のように$!logonForm.username、$!logonForm.passwordがHTMLで展開されているのが分かる。(5)

  1. 例をそのまま使用したため、エラーメッセージ出力用マクロ#errorMarkup()が表示されている。

1.2.2 simteecの応用

strutsのフォームには、String、Integer, Doubleのような単純なオブジェクトだけではなくビジネスロジックに基づいたBeanが設定されることが多い。simteecには、ユーザ定義のクラスオブジェクトを動的に生成するメカニズムが提供されている。次のsimteec_classessがユーザ定義クラスオブジェクトの宣言部である。Velocityテンプレートで使用する変数名とそれに対応するクラス名をハッシュマップ定義の形式で記述する。

	simteec_classes   = %(
		"member" => "jp.co.pwv.estore.business.Member"
		);				
			

ユーザ定義クラスMemberを使ったテンプレートとプロパティファイルを以下に示す。テンプレートの先頭で、memberForm.memberに$memberによって生成されたMemberオブジェクト$userがセットされる。simteec_classessのオブジェクト生成では、引数なしのデフォルトコンストラクタが必須であり、生成した後に属性をセットする必要がある。

リスト 1.2.2.1 memberForm.vm
#set($user = $member)
#set($user.Name = "Hiroshi TAKEMOTO")
#set($user.Address = "中野区中央")
#set($memberForm.member = $user)

<html>
    <head>
        <title>メンバー入力</title>
    </head>

    <body>
        <h3>メンバー入力</h3>
        <form method="POST" action="$link.setAction('memberSubmit_vm')">
        <table border="0">
            <tr>
                <th align="right">
                    Name:
                </th>
                <td align="left">
                    <input type="text" name="name" value="$!memberForm.member.name">
                </td>
            </tr>
            <tr>
                <th align="right">
                    Address:
                </th>
                <td align="left">
                    <input type="text" name="address" value="$!memberForm.member.address">
                </td>
            </tr>
            <tr>
                <td align="right">
                    <input type="submit" value="Submit" name="submit">
                </td>
                <td align="left">
                    <input type="reset" value="Reset" name="reset">
                </td>
            </tr>
        </table>
        </form>
    </body>
</html>
リスト 1.2.2.2 memberForm.prop
## simteecコンフィグの設定
simteec_classes   = %(
	"member" => "jp.co.pwv.estore.business.Member"
	);

memberForm   = %(
	"member" => "dummy text"
	);

simteec_output   = "memberForm.html";
simteec_overwrite="true";
simteec_template = "PROJECT:/vm/memberForm.vm";

1.2.3 simteecとDBの連携

Memberの様な単純なBeanの場合では属性の設定をVelocityテンプレートのVTLで記述しても問題はないが、テーブルの要素としてユーザ定義Beanのリストを使用する場合には、これでは大変である。そこで、EDbUtilを使ったDBManagerクラスを作成し、そのインスタンを使ってDBにアクセスする方法を紹介する。先のmemberFormのVTLの部分をDBManagerを使った方式で書き直すと次のようになる。

#set($manager = $dbmanager)
$manager.addHelper($member)
#set($user = $manager.loadObject($member, 0))
#set($memberForm.member = $user)				
			

EDbUtilsでは複雑なBeanも非常に簡単にロードすることができるため、VTLの記述もこれ以上複雑にはならないはずである。dbForm.propは、次のようになる。memberFormとの違いは、dbmanagerの定義が追加されたことだけである。

リスト 1.2.3.1 dbForm.prop
## simteecコンフィグの設定
simteec_classes   = %(
	"member" => "jp.co.pwv.estore.business.Member",
	"dbmanager" => "jp.co.pwv.estore.util.DBManager"
	);

simteec_output   = "dbForm.html";
simteec_overwrite="true";
simteec_template = "dbForm.vm";

次に大きな問題となるのは、デザイナーの担当者がDBを操作することが容易だろうかという疑問である。これに対する答えは、HSQLDBの提供するcsvファイルをDBのテーブルと同様に扱えるようにする機能である。(6)

HSQLDBのテーブル作成を次のように修正する。(7)

CREATE TEXT TABLE T_MEMBER(ID INTEGER,ADDRESS VARCHAR,NAME VARCHAR)
SET TABLE T_MEMBER SOURCE "member.csv"				
			

これで、member.csvを修正することでデザイナーがテストデータを作成することが可能となる。

  1. 残念なら、文字コードがUnicodeであるため、Excelで日本語が正しく処理されない。
  2. テキストエディタで、HSQLDBのDBファイル記述ファイル(.script)を開き、T_MEMBERのCREATE TABLE文を検索し、修正するだけでよい。

1.2.4 simteecの課題

当初の目標であった、ビューレンダリングをモデル、コントロールの作業から分離することが可能であることは検証できたが、課題もまた明らかになった。

1.3 Velocityとpnutsの連携

simteecの課題を解決するために、pnutsを使ってVelocityのテンプレートの展開とVelocity Contextへの設定を行う方法を紹介する。

1.3.1 pnutsを使ったプロパティ定義

simteecでは、独自の文法でVelocity Contextに登録する変数と値を定義していたが、同様の処理はpnutsを使っても可能である。pnutsは、javaクラスのテスト用スクリプト言語として開発された経緯から、javaクラスとの連携が容易であり、言語仕様も柔軟である。そこで、simteecのプロパティフィファイルでの変数設定をpnutsのプログラム断片を使って定義することにする。(8)

リスト 1.3.1.1 pnutsForm.pnuts
1: import("jp.co.pwv.estore.business.Member")
2: import("java.util.HashMap")
3: 
4: // テンプレートの指定
5: velpnuts_template = "vm/pnutsForm.vm"
6: velpnuts_output = "vm/pnutsForm.html"
7: 
8: // テンプレートにセットする変数の配列を返す
9: function setupProperties() {
10: 	// Memberオブジェクトの生成
11: 	member = Member()
12: 	member.setName("Hiroshi TAKEMOTO")
13: 	member.setAddress("中野区中央")
14: 	// memberFormにMemberオブジェクトをセット
15: 	memberForm = HashMap()
16: 	memberForm.put("member", member)
17: 
18: 	return ([
19: 				["member", member],
20: 				["memberForm", memberForm]
21: 			])
22: }

pnuts言語に馴染みの無い型にも分かるように順を追って説明する。

  1. プログラム断片という表現は、setupProperties関数のみを定義しているからである。

1.3.2 pnutsを使ったVelocityテンプレート展開

pnuts版Velocityテンプレート展開プログラムは、Eclipseだけではなくコマンドラインからも起動することができる。

pnuts -m pnuts.util vel-pnuts.pnuts pnuts版のプロパティ定義ファイル名		
			

velpnuts_outputが定義されていない場合には、標準出力に表示される。pnuts版Velocityテンプレート展開プログラムのソースを以下に示す。

リスト 1.3.2.1 vel-pnuts.pnuts
import("java.io.*")
import("org.apache.velocity.app.Velocity")
import("org.apache.velocity.VelocityContext")
import("javax.swing.*")

file = $args[1]
loadFile(file)

if (defined("setupProperties")) {
	// テンプレートが指定されない場合には、スキップ
	if (!defined("velpnuts_template")) {
		continue
	}
	// Velocityの初期化
	Velocity::init()
	context = VelocityContext()
	// 関数setupPropertiesで返された変数をVelocity Contextにセットする
	pairList = setupProperties()
	foreach pair (pairList)  {
		context.put(pair[0], pair[1])
	}
	// 出力ファイルが指定されていない場合には、結果を標準出力に表示する
	if (defined("velpnuts_output")) {
		w = PrintWriter(FileOutputStream(velpnuts_output))		
		Velocity::mergeTemplate(velpnuts_template, "Shift_JIS", context, w)
		w.close()
	}
	else {
		w = StringWriter()	
		Velocity::mergeTemplate(velpnuts_template, "Shift_JIS", context, w)
		println(w)
	}	
}
else {
	println("プロパティ設定ファイルにsetupProperties関数が定義されていません。")
}

参考文献

[1]栗林 克明. JakartaプロジェクトカンタンVelocity : テンプレートエンジンVelocityによるWebアプリケーション開発. 秀和システム,