テンプレート切り替え機能を拡張する

Mayaaには一つのmayaaで複数のテンプレートを切り替える機能がある。(参考: 3-10. テンプレート切り替え : Documentation - JavaServer Templates "Mayaa") たとえば利用者の使用言語に合わせてその言語用のHTMLを使う、というような用途が想定されているらしい。


使い方としては、たとえばhello.mayaaファイルのm:mayaaタグの属性にm:templateSuffix="${locale}"などと書く事で、locale="en"の時にhello$en.html、locale="ja"の時にhello$ja.htmlを表示に使用することができる。また、hello$○○.htmlが無い場合にはhello.htmlファイルが使用される。


templateSuffixで指定した種類に対応するHTMLファイルが無い場合の挙動だけど、$から.htmlの間が完全一致か、$なしの.htmlかの2ファイルしか選択されないのは少し不便だ。例えばロケールならjaを探す→enを探す→$なしを探す、のように、複数のファイルを指定した順で検索して欲しい場合もある。携帯電話用のサイトなら、DoCoMo QVGA用→DoCoMo全機種用→携帯全機種用などのように検索して欲しい。そこで、テンプレート切り替え部分のクラスを拡張して、カスケードが可能なようにしてみた。

機能の説明

m:templateSuffixで指定した文字列を"_"で分解して、前にある要素を優先してテンプレートを順に検索する。


例えば、hello.mayaaのm:templateSuffixに"mobile_docomo_high"を指定した場合、以下の順序で検索をおこない、見つかったファイルをテンプレートに使用する。

  1. hello$mobile_docomo_high.html
  2. hello$mobile_docomo.html
  3. hello$mobile_hight.html
  4. hello$mobile.html
  5. hello$docomo_high.html
  6. hello$docomo.html
  7. hello$high.html
  8. hello.html

ソースコードと設定

(動作確認は一応したけど実戦投入はまだです。)


org.seasar.mayaa.impl.engine.PageImplのサブクラスを作り、getTemplate()メソッドをオーバーライドして処理を差し替える。

package sample.mayaa.engine;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.seasar.mayaa.engine.Template;
import org.seasar.mayaa.impl.engine.PageImpl;
import org.seasar.mayaa.impl.engine.PageNotFoundException;
import org.seasar.mayaa.impl.util.StringUtil;

public class SuffixCascadePageImpl extends PageImpl {
    private static final String SEPARATOR = "_";
    private static final long serialVersionUID = -4792634398812735416L;

    @Override
    public Template getTemplate(String originalSuffix, String extension) {
        if (originalSuffix == null) {
            originalSuffix = "";
        }
        if (extension == null) {
            extension = "";
        }
        if (QM_MAYAA.getLocalName().equals(extension)) {
            return null;
        }
        Template template = null;
        Iterator<String> suffixes = getSuffixes(originalSuffix).iterator();
        while (suffixes.hasNext()) {
            template = getTemplateFromFixedSuffix(suffixes.next(), extension);
            if (template != null) {
                return template;
            }
        }
        if (StringUtil.hasValue(originalSuffix)) {
            template = getTemplateFromFixedSuffix("", extension);
        }
        if (template == null) {
            throw new PageNotFoundException(getPageName(), extension);
        }
        return template;
    }
    /**
     * 指定された文字列をセパレータ'_'で切り分け、種類文字列の候補を再帰的に生成する。
     * 例えば"mobile_docomo_high"を指定した場合、以下の順序で文字列が含まれるListを戻す。
     * <ol>
     * <li>"mobile_docomo_high"</li>
     * <li>"mobile_docomo"</li>
     * <li>"mobile_hight"</li>
     * <li>"mobile"</li>
     * <li>"docomo_high"</li>
     * <li>"docomo"</li>
     * <li>"high"</li>
     * </ol>
     * @param suffix 指定された種類文字列
     * @return 種類文字列候補のList
     */
    protected List<String> getSuffixes(final String suffix) {
        int pos = suffix.indexOf(SEPARATOR);
        if (pos == -1) {
            return  Arrays.asList(new String[] {suffix});
        }
        String head = suffix.substring(0, pos);
        String rest = suffix.substring(pos + 1);
        List<String> subList = getSuffixes(rest);
        List<String> results = new ArrayList<String>();
        for (String tail : subList) {
            results.add(head + SEPARATOR + tail);
        }
        results.add(head);
        results.addAll(subList);
        return results;
    }
}


META-INF/org.seasar.mayaa.provider.ServiceProviderに上のクラスを指定する。
(下のXMLのpageClassの行)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE provider
    PUBLIC "-//The Seasar Foundation//DTD Mayaa Provider 1.0//EN"
    "http://mayaa.seasar.org/dtd/mayaa-provider_1_0.dtd">
<provider>
    <engine>
        <parameter name="convertCharset" value="true"/>
        <parameter name="pageClass" value="sample.mayaa.engine.SuffixCascadePageImpl"/>
    </engine>
</provider>

おしまい。

使い方サンプル

ServletのFilterなどで、User-Agentなどから割り出した端末スペックをリクエストにセット

// 例
public class DeviceFilter implements Filter {
....
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
	// agent判定
        String carrier; // "docomo" or "au" or "thirdforce"
        String resolution; // "high" or "low"
....
	// agent設定
        request.setAttribute("agent", "mobile_" + carrier + "_" + resolution);

        chain.doFilter(request, response);
    }


mayaaファイルでは、リクエストの"agent"値をtemplateSuffixに指定

例: hello.mayaa

<?xml version="1.0" encoding="Windows-31J"?>
<m:mayaa xmlns:m="http://mayaa.seasar.org"
	m:templateSuffix="${agent}">
....
</m:mayaa>

これでファイルを配置するかどうかで、hoge$mobile_docomo.html、hoge$mobile_au.htmlなどのキャリア毎の出し分けも、hoge$mobile_hight.html, hoge$mobile_low.htmlなどの画面サイズでの出し分けも(あるいはその組み合わせも)出来る。

感想その他

  • Mayaaの拡張ポイントの豊富さは異常。
  • PageSourceDescriptorとFileSourceDescriptorを拡張するやりかたも出来そう。mayaaファイル側も切り分けたい場合はその方が良いかも。
  • まだ確認してないけど、パフォーマンスに影響が出ないかが若干気になる。