テンプレートエンジンfreemarker2.*で自作のタグを作成

2010/05/02 18:15

軽量テンプレートエンジン、freemarker2.*でJavaのカスタムタグ等の様に、特殊な動きを実装した自前のタグを定義しテンプレートの中で使える様にする方法を以下に示します。

テンプレートからの呼び出し方法は<@定義名 />となります。

まず、現時点で方法は2つあります。

一つはTemplateDirectiveModel、もう一つはTemplateTransformModelを実装する方法です。

freemarkerのバージョンが2.1くらいまではTemplateDirectiveModelが使えない為、TemplateTransformModelでやることになりますが、現在TemplateTransformModelは非推奨なので、なるべくTemplateDirectiveModelを使用しましょう。以下に実装方法を両方とも記載いたします。

【TemplateDirectiveModelでの実装】

1.TemplateDirectiveModelを継承したクラスを作成

/** ここから */

import java.io.IOException;
import java.util.Map;

import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;

public class CustomDerectiveModel implements TemplateDirectiveModel{   
    @Override
    public void execute(Environment env, Map map, TemplateModel[] tms,
        TemplateDirectiveBody tdb) throws TemplateException, IOException {
    }
}

/** ここまで */

 

2. freemarker.template.Configurationクラスのインスタンス生成

Configuration config = new Configuration();
config.setSharedVariable("cdm", new CustomDerectiveModel());

※1 "cdm"は任意。テンプレート呼び出し時の名前
※2 CustomDerectiveModelは上記で作成したクラス

 

3. freemarkerのテンプレートは以下の通り記述

/** ここから */

<@cdm attrA="属性値1" attrB="属性値2">bodyです</@cdm>

/** ここまで */

上記からCustomDerectiveModelのexecuteメソッドが呼び出されます。

attrA~の属性値1~はMap、「bodyです」はTemplateDirectiveBodyに入っています。

あとはWriterに書き込んでいくいくだけです。そこでの実装で注意する点はスレッドセーフにすることです。

サンプルソースは、英語ですが本家ドキュメントが詳しいので参考にして下さい。http://freemarker.sourceforge.net/docs/pgui_datamodel_directive.html

 

【TemplateTransformModelでの実装】

1.TemplateTransformModelを継承したクラスを作成

/** ここから */

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateTransformModel;

public class CustomTransformModel implements TemplateTransformModel{

    @Override
    public Writer getWriter(final Writer out, Map map)
            throws TemplateModelException, IOException {
        final StringBuilder buf = new StringBuilder();
        return null;
    }
}

/** ここまで */

 

2. freemarker.template.Configurationクラスのインスタンス生成

Configuration config = new Configuration();
config.setSharedVariable("ctm", new CustomTransformModel());

※1 "ctm"は任意。テンプレート呼び出し時の名前
※2 CustomTransformModelは上記で作成したクラス

 

3. freemarkerのテンプレートは以下の通り記述

/** ここから */

<@ctm attrA="属性値1" attrB="属性値2" />

/** ここまで */

上記からCustomTransformModelのgetWriterメソッドが呼び出されます。

attrA~の属性値1~はMapに入っています。当方式ではタグのネストは許されない様です。※ java.io.IOException: This transform does not allow nested content.

あとはWriterに書き込んでいくいくだけです。こちらも注意点はスレッドセーフにすることです。

サンプルソースは、英語ですが本家ドキュメントが詳しいので参考にして下さい。http://freemarker.sourceforge.net/docs/api/freemarker/template/TemplateTransformModel.html

以上です。

struts2でファイルアップロード(FileUpload Interceptorの設定、初期値、制限)

2010/04/24 01:39

struts2のファイルアップロード用インターセプターに関して、基本的には英語だが以下にまとめられている。

http://struts.apache.org/2.0.14/docs/file-upload.html

・struts2のFileUpload Interceptorのアップロード可能なファイルサイズ制限は、デフォルトでは2MBに設定されている。また、この値は個々ではなく複数アップロードした場合はその合計値

propertiesファイルで以下を設定することによりオーバーライド可能。

struts.multipart.maxSize=任意の数値(Byte)
※ struts.xmlに以下を追加する方法もある。
<constant name="struts.multipart.maxSize" value="任意の数値(Byte)" />

因みに最大サイズの制限を無くすには"-1"を指定する。FileUploadBase.javaのソースコードをみて頂ければ仕様がわかると思う。

また、FileUpload Intercepterで曲者なのが、org.apache.commons.fileupload.FileUploadBaseにデフォルトでハードコードされているエラーメッセージだ。無理やりAction周りでリプレイスする荒業もあるが、そんなことはしちゃ駄目だ。

このInterceptorはmultipartのリクエストを処理する段階で呼ばれる(かなり早い段階で呼ばれる)為、MultiPartRequestで行われるparseメソッド呼び出し対象クラスを自前のものに変更する。

propertiesファイルで以下を設定することにより呼び出しクラスを変更可能。デフォルトのクラスを継承したクラスに変更し、メソッドをオーバーライドしよう。

struts.multipart.parser=自前で用意したクラスのFQCN

といった具合です。

更に、一時ファイルの保存ディレクトリを変更する場合は

struts.multipart.saveDir=保存先ディレクトリ

を記述します。

 

フォームから値を取得する方法は

・File [fileName]  -- ファイルオブジェクト
・String [fileName]ContentType -- コンテンツタイプ
・String [fileName]FileName -- アップロードフォームのファイル名

のメンバ変数とセッターゲッターを用意すると、各々自動でバインドされます。
※ 別にFileオブジェクトだけでも可能。[fileName]部分は任意。

正規のドキュメントが英語のものしかない為情報が少ないですが(しかも本家にも書かれていないこともある)、struts2って素晴らしいですね。

大体は変なことをしなくても拡張可能な様に実装されていると思われます。

でもすごく気に入らない実装もあった気がするんだよなぁ。。忘れちゃったけど。

struts2において、フォームの値を入れ子構造の配列で受け取る際の小技

2010/04/23 17:29

Javaのフレームワークstruts2を利用した開発において、

フォームの値をプログラムで取得する際は、getModel()でフォームの値をマッピングした型を返すパターンが多いと思います。

この場合で配列を利用する際は、

【HTML】

--------------------------------------------------------------

<input type="text" name="model[0].name">

<input type="text" name="model[0].link">

<input type="text" name="model[1].name">

<input type="text" name="model[1].link">

<input type="text" name="model[2].name">

<input type="text" name="model[2].link">

--------------------------------------------------------------

【Java】

--------------------------------------------------------------

アクション

・・・

private List<HogeForm> hogeForm = new ArrayList<HogeForm>();

@Element( value = HogeForm.class )

public Object getModel() {

    return hogeForm;

}

・・・

※ HogeFormクラスにはname, linkのメンバ変数を用意

--------------------------------------------------------------

みたいな感じでアノテーション@Element( value = HogeForm.class ) を利用することで、階層を浅くする事ができるのですが、あまり知られていない様です。

現場でよくみかけるのは下記の様なソースです。

【HTML】

--------------------------------------------------------------

<input type="text" name="hogeListForm.form[0].name">

<input type="text" name="hogeListForm.form[0].link">

<input type="text" name="hogeListForm.form[1].name">

<input type="text" name="hogeListForm.form[1].link">

<input type="text" name="hogeListForm.form[2].name">

<input type="text" name="hogeListForm.form[2].link">

--------------------------------------------------------------

【Java】

--------------------------------------------------------------

アクション

・・・

private HogeListForm hogeListForm = new HogeListForm();

public Object getModel() {

    return hogeListForm;

}

・・・

※   HogeListFormクラスにはFormクラス型のリストのメンバ変数を用意、Formクラスにはname, linkのメンバ変数を用意

--------------------------------------------------------------

是非、アノテーション@Elementを使用してみて下さい。