Agusa Lab. > Webware Project > GrapeWeb
 

Velocity

概要

Velocityは汎用的なテンプレートエンジンです。"汎用"とは、HTML、XML、 プレインテキストなどフォーマットを問わないことを意味します。 本チュートリアルではテンプレートエンジンの基本的な使用法からWebアプリケーションへの応用まで 見ていきます。

テンプレートエンジン

テンプレートエンジンとはテンプレートを埋めて文書として出力する仕組みを指します。 VelocityではテンプレートをVTL(Velocity Template Language)で記述します。 VTLについては後述します。このテンプレートに対して埋めるデータの集合をコンテキストと 呼びます。コンテキストにあるデータをテンプレートに埋めることをマージと呼びます。 つまり、マージする仕事を行うのがテンプレートエンジンです。

テンプレートエンジン

どのように使われているか

Velocityは他のプロジェクトの基盤技術として使われています。

プロジェクト名 種別 用途
Torque O/Rマッピングフレームワーク SQLやJavaソースの生成
Cayenne O/Rマッピングフレームワーク Javaソースの生成(SQLを生成しているかは資料が無い)
Scarab Issue Tracking System HTMLの生成。あと、Torqueも入っているためSQLとJavaソースの生成もVelocityが やっている。一年と三ヶ月もの間更新が滞っていたが2005/7/5にやっと更新。

そのほか VelocityのWikiページで報告されているだけでも 35のプロジェクトがVelocityを利用しています。

Struts、Spring、TurbineなどのフレームワークはVelocityの利用をサポートしています。 これらのことから分かるようにVelocityは(少なくともオープンソース界では)広く利用されています。

今回はWebチュートリアルなので、Strutsからの利用法も見ていきます。

基本的な使い方

ここではVelocityの基本的な使い方を例を通して見ていきます。

まずVTL(Velocity Template Language)を覚えます。シェルスクリプトに似た構文を持ちます。 大きく"リファレンス"と"ディレクティブ"に分類されます。

まずリファレンスを以下に示します。

用語 意味
リファレンス $var や${var}のような形でコンテキストへの参照を表す。 コンテキストにおいてvarに"val"が割り当てられていればテンプレートにおいて $varを"val"に置き替える。 コンテキストにvarが無い場合は$varがそのまま出力される。
沈黙リファレンス $!var のように!をつけて記述する。 基本的にリファレンスと同じだが、こちらはコンテキストに無い場合何も出力しない。
プロパティ $var.prop や ${var.prop} の形でvarのプロパティpropを参照する。 コンテキストにおいてvarがHashtableならpropにキーを使うことで値を参照する。 コンテキストにおいてvarがJavaBeansならgetProp()を呼ぶ。
メソッド $var.method() の形でvarのメソッドを呼ぶ。

次にディレクティブを示します。

構文 意味
## コメント
一行コメント。出力されない。
#*
   コメント
*#
複数行コメント。出力されない。
#foreach($変数1 in $変数2)
   ・・・・・・・・・・・・
#end
変数2から一つづつ順番に取り出して繰り返し実行する。変数1が変数2から取り出した値を参照する。
#if(条件式)
   ・・・・・・・・・・
[#elseif(条件式)]
   ・・・・・・・・・・
[#else]
   ・・・・・・・・・・
#end
条件分岐。
#include(ファイル1,ファイル2, ・・・)
他のテキストをインクルードする。 インクルードされるファイルにVTL構文があってもVTL構文として解釈されず、 そのまま表示される。
#parse(ファイル)
他のファイルをVTL構文を解析してインクルードする。
#set($変数 = 値)
変数に値を代入する。

以上のVTLを用いてテンプレートを書いてみます。 ここでは、掃除係用のメールテンプレートを作成します。

まずテンプレートを作成します。

$name@掃除係です。

今週の掃除当番は
#foreach ( $student in $studentList )
・$student.name
#end
です 
よろしくお願いします。 

$!memo
		

つぎにマージするためのJavaファイルを用意する。

package test;

import java.io.*;
import java.util.*;
import org.apache.velocity.*;
import org.apache.velocity.app.*;

public class Mail {
    
    public static void main(String[] args) {
        Mail t = new Mail();
        try {
            t.go(args[0]);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }

    public void go(String templateFile) throws Exception {
        ///////////////////////
       // Velocityの初期化 //
        ///////////////////////
        Properties p = new Properties();
        // エンコードの指定
        p.setProperty("input.encoding", "UTF-8");
        Velocity.init(p);
        
        // Velocityのコンテキストの生成
        VelocityContext context = new VelocityContext();
        
        // Velocityコンテキストに置き換え用のオブジェクトを登録
        ArrayList studentList = new ArrayList();
        
        Student student1 = new Student();
        student1.setName("Aさん");
        studentList.add(student1);

        Student student2 = new Student();
        student2.setName("Bさん");
        studentList.add(student2);
        
        Student student3 = new Student();
        student3.setName("Cさん");
        studentList.add(student3);

        context.put("studentList" , studentList);
        context.put("name" , "掃除大臣");
        context.put("memo" , "今週は大掃除です。\n皆さん張り切って掃除しましょう!");

        // テンプレートの読み込み
        Template template = Velocity.getTemplate(templateFile);
        
        StringWriter writer = new StringWriter();
        
        // マージ
        template.merge(context, writer);
        
        // 出力
        System.out.println(writer.toString());
    }
}
		

package test;
public class Student {
   private String name;
   
   public String getName() {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
}			
		

これを実行すると以下のような結果が得られる。

掃除大臣@掃除係です。

今週の掃除当番は
・Aさん
・Bさん
・Cさん
です 
よろしくお願いします。 

今週は大掃除です。
皆さん張り切って掃除しましょう!
		

Strutsとの連携

ここではStrutsとVelocityを連携する方法を紹介します。以下に連携の仕組みを示します。

StrutsとVelocityの連携

まずリクエスト(.../*.do)をStrutsのActionServletが受けVelocityのサーブレットにフォワードします。 このときStrutsのActionクラスはコンテキストを生成します。 次にVelocityのサーブレットがテンプレートを読み込みActionクラスで作ったコンテキストと合わせて ページを生成します。

ここでは以下のようなサンプルアプリケーションを作成します。

サンプルアプリのページ遷移図

まず、web.xmlにstrutsのActinServletとVelocityのサーブレットを登録し、 *.doをstrutsのActionServletに、*.htmlをVelocityのサーブレットにマッピングします。 (ただし、ここではEncodableActionServletを使っています。)

<?xml version="1.0"?>

<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
	<servlet>
		<servlet-name>action</servlet-name>
		<servlet-class>grape.struts.action.EncodableActionServlet</servlet-class>
		<init-param>
			<param-name>config</param-name>
			<param-value>/WEB-INF/struts-config.xml</param-value>
		</init-param>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet>
		<servlet-name>velocity</servlet-name>
		<servlet-class>org.apache.velocity.tools.view.servlet.VelocityViewServlet</servlet-class>
		<init-param>
			<param-name>org.apache.velocity.properties</param-name>
			<param-value>/WEB-INF/velocity.properties</param-value>
		</init-param>
		<load-on-startup>3</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>action</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

	<servlet-mapping>
		<servlet-name>velocity</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>
</web-app>
		

Velocityのプロパティの設定はweb.xmlで登録したように/WEB-INF/velocity.properties に記述します。ここではエンコーディングを指定します。

-- velocity.properties --
input.encoding=UTF-8
output.encoding=UTF-8
		

ページテンプレートを用意します。

-- index.html --
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
		<title>占いアプリ</title>
	</head>
	<body>
		<form action="../auguryAction.do">
			<select name="type">
				<option value="day">今日</option>
				<option value="week">今週</option>
				<option value="month">今月</option>
			</select>の運勢を
			<input type="submit" value="占う"/>
		</form>
	</body>
</html>			
		

-- result.html --
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
		<link rel="stylesheet" type="text/css" href="augury.css"/>
		<title>占いアプリ</title>
	</head>
	<body>
		<h3>結果</h3>
		<p>
			#if($type == "day")
				今日の運勢は $result です。
			#elseif($type == "week")
				今週の運勢は
					<table border="1">
						<tr>
							#foreach ($day in $daysOfWeek)
								<td align="center" style="width: 30pt">$day</td>
							#end
						</tr>
						<tr>
							#foreach ($result in $results)
								<td align="center" >$result</td>
							#end
						</tr>
					</table>
			#elseif($type == "month")
				今月の運勢は $result です。
			#end
		</p>
	</body>
</html>		
		

つぎにstruts-configのアクションマッピングを示します。

<?xml version="1.0"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
  <data-sources>
  </data-sources>
  <form-beans>
  </form-beans>
  <global-exceptions>
  </global-exceptions>
  <global-forwards>
  </global-forwards>
  <action-mappings>
    <action path="/auguryAction" type="test.AuguryAction">
      <forward name="result" path="/pages/result.html"/>
    </action>
  </action-mappings>
  <controller/>
</struts-config>
		

次にアクションクラスを示します。

package test;
import java.util.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;

public class AuguryAction extends Action {
    public ActionForward execute(ActionMapping mapping,
                                 ActionForm form, 
                                 HttpServletRequest request, 
                                 HttpServletResponse response) throws Exception {
        Random random = new Random();
        String type = (String) request.getParameter("type");
        if (type.equals("week")) {
            String[] daysOfWeek = {"日", "月", "火", "水", "木", "金", "土"};
            String[] results = new String[7];
            for (int i = 0; i < results.length; i++) {
                results[i] = augur(random.nextDouble());
            }
            request.setAttribute("daysOfWeek", daysOfWeek);
            request.setAttribute("results", results);
        } else {
            String result = augur(random.nextDouble());
            request.setAttribute("result", result);
        }
        request.setAttribute("type", request.getParameter("type"));
        return mapping.findForward("result");
    }
    
    public String augur(double value) {
        if (value >= 0 && value <= 0.2) {
            return "凶";
        } else if (value > 0.2 && value <= 0.6) {
            return "吉";
        } else if (value > 0.6 && value <= 0.9) {
            return "中吉";
        } else if (value > 0.9 && value <= 1.0) {
            return "大吉";
        } else {
            return "よくわからん";
        }
    }
}
		

おわりに

Velocityは非常に汎用的で簡単です。Struts in Actionにおいて、"Velocityはページデザイナが Javaプログラマでない、もしくはJSPによるページ作成を得意としない場合に効果的である"と紹介されて いるように扱いやすいものとなっています。これは構文の少なさによるものです。 (せめてページデザイナはVTLくらい書けてほしい。。。) Velocityならすぐに覚えられるので、文書の生成にはぜひVelocityを活用してみてください。

by Ryo Suetsugu