Struts2+Velocityでのカスタムタグ拡張の初歩

Struts2のカスタムタグ

Struts2では様々なカスタムタグが用意されていて、
しかも、それらはJSPFreeMarker・Velocityの3つのテンプレートエンジンで使える。
詳しくは、http://struts.apache.org/2.x/docs/tag-developers-guide.htmlらへん参照。


例えばformタグなら、
JSPの場合はこう

<s:form>
  <s:textfield name="username" label="Login name"/>
  <s:password name="password" label="Password"/>
  <s:submit value="Login" align="center"/>
</s:form>

Velocityの場合はこう

#sform ()
  #stextfield ("name=username" "label=Login name")
  #spassword ("name=password" "label=Password")
  #ssubmit ("value=Login" "align=center")
#end

という風に書き方は違っても同じ機能が使えて素敵ですね。

JSPとVelocityでの違い

さっきのところで終われば本当に素敵なんだが、
こういう紹介は基本的に都合のいいところしか見せていないので、
そうは問屋が卸さず嵌りに嵌って、
非生産的な一日を送るなどという事が稀に良くあるらしい。


JSPの場合は同じタグがブロックタグとしてもインラインタグとしても使える。
URLタグを例にとると、
こんな感じの両方とも書ける。

<s:url action="someAction">
  <s:param name="paramName" value="%{paramValue}" />
</s:url>
<s:url action="someAction" includeParams="get"/>


一方、Velocityの場合は一つのカスタムタグは、
ブロックタグもしくはインラインタグのどちらかとしてしか使えない。
これは、Velocityの場合は、どのブロックタグであっても#endで閉じるので、
#endで閉じているかもしれないし、閉じていないかもしれない
というのを解釈出来ないからだと思われる。
で、各カスタムタグがブロックタグかインラインタグかは事前に決まっている。


ブロックタグとして使いたいカスタムタグがインラインタグだったときにはたまったものではない。
先ほどのURLタグが実はそうなので、以下のようにして、

#surl ("action=someAction")
  #sparam ("name=paramName" "value=$paramValue")
#end

動的にパラメータを付けたURLを作るといったことができない。
ありえん。


というわけで、既存のカスタムタグのブロックタグ版みたいのを定義してやる必要がある。

既存のカスタムタグのブロックタグ版の定義

とても簡単


こんなclassを作ってやって

package examples;

import org.apache.struts2.views.velocity.components.URLDirective;
import static examples.BlockTagLibrary.PREFIX;

public class BlockURLDirective extends URLDirective {

    @Override
    public String getName() {
        return PREFIX + getBeanName();
    }

    @Override
    public int getType() {
        return BLOCK;
    }

}
package examples;

import java.util.Arrays;
import java.util.List;

import org.apache.struts2.views.DefaultTagLibrary;

public class BlockTagLibrary extends DefaultTagLibrary {

    public static String PREFIX = "b";
    @SuppressWarnings("unchecked")
    @Override
    public List<Class> getVelocityDirectiveClasses() {
        Class[] directives = new Class[] {
                BlockURLDirective.class
        };
        return Arrays.asList(directives);
    }

}

struts.xmlにこんな設定を追加してやればよい。

<bean type="org.apache.struts2.views.TagLibrary" name="b" class="examples.BlockTagLibrary" />


何をやっているかというと、の前に元々どうなっていたか説明すると、
struts-default.xmlにこのような記述がある。

<bean type="org.apache.struts2.views.TagLibrary" name="s" class="org.apache.struts2.views.DefaultTagLibrary" />

DefaultTagLibrary classのgetVelocityDirectiveClassesメソッドは全カスタムタグ分の
Directive classの配列を返していて、
各Directive classではカスタムタグのコンポーネントのclassとVelocityカスタムタグを紐付けている。


struts-default.xmlの場合は"s"というprefixのカスタムタグ群として、
DefaultTagLibrary経由でDirective class群を登録している。
このDirective classのgetNameメソッドの戻り値がタグ名であり、
getTypeメソッドの戻り値がブロックタグかインラインタグかの設定である。


今回は"b"というprefixでカスタムタグ群(ただし今回追加したのは1個)を登録し、
カスタムタグのコンポーネントのclassとの紐付けはsurlと同じで良いので、
登録対象のDirective classは、URLDirectiveを継承して、
getNameメソッド・getTypeメソッドのみオーバーライドとした。


ブロックタグ化したいインラインタグが他にも出てきたら、
同じようにDirective classの継承classを作って、
BlockTagLibrary経由で登録してやればよい。