Webエンジニア susumuis の技術ブログ

このブログの内容は個人の見解であり、所属する組織の公式見解ではありません

Mayaa ファイルの共通化

 同じ機能のテンプレートが複数セット存在する場合を考えます。例えば、/1/A.htmlと、/2/A.htmlは、デザインが違いますが、全く同じ機能だとします。このとき、/1/A.mayaa を /2/A.mayaa に複製するのが基本です。しかし、それだと、/1/A.mayaaを修正したときは、同時に/2/A.mayaaも修正しなくてはなりません。/3/A.mayaa、/4/A.mayaaのように、さらに複製されていた場合はなおさら厄介です。
 
 複数のテンプレートが共通のMayaaファイル(Specファイル?)を参照する場合、手っ取り早い方法としては、templateSuffixを使う方法があります。
http://mayaa.seasar.org/documentation/template_suffix.html
 しかし、上記のようにフォルダでテンプレートをグループ分けしたい場合は、suffixは使いにくいです。

 シンボリックリンクによる方法も簡単です。これなら、Mayaaのカスタマイズなしに、すぐに使えます。しかし、Windows上でテストできない点が私はネックだと思いました。Javaシステムなので、OSやサーバに依存しないで解決したいです。

 この度、Mayaa ユーザメーリングリストにて、次のように質問させていただきました。
 http://ml.seasar.org/archives/mayaa-user/2009-December/000868.html
 suga様の回答(ありがとうございます。)を参考に、SourceDescriptorのカスタマイズによって解決しました。以下ソースを貼っておきます。

package mypackage;

import java.io.InputStream;
import java.util.Date;

import org.seasar.mayaa.impl.source.PageSourceDescriptor;
import org.seasar.mayaa.source.SourceDescriptor;

public class MyPageSourceDescriptor extends PageSourceDescriptor {

	// ルートのmayaaファイルを探しに行くディスクリプタ
	private SourceDescriptor rootDescriptor = new PageSourceDescriptor();

	// 注意:SystemIDはmayaaファイルの絶対パス
	/**
	 * ソースSystemIDを設定する。
	 * 
	 * @param systemID
	 */
	@Override
	public void setSystemID(String systemID) {
		super.setSystemID(systemID);

		// ルートのパス取得。
		String rootId = getRootId(systemID);
		if (rootId != null) {
			rootDescriptor.setSystemID(rootId);
		} else {
			rootDescriptor.setSystemID(systemID);
		}
	}

	// 要求パスからルートのパスを解決する。
	private String getRootId(String systemId) {
		if (systemId.startsWith("/client_info/")) {
			int pt = systemId.indexOf("/", "/client_info/".length());
			if (pt != -1) {
				return systemId.substring(pt);
			}
		}
		return null;
	}

	/**
	 * ソースが存在するかどうかを取得する。
	 * 
	 * @return ファイルが存在すればtrue。無ければfalse。
	 */
	@Override
	public boolean exists() {
		// ルートの方が見つかりやすいから、処理高速化のためルート優先
		return rootDescriptor.exists() || super.exists();
	}

	/**
	 * ファイルのインプットストリームを取得する。
	 * 
	 * @return ストリーム。もしファイルが無い場合は、null。
	 */
	@Override
	public InputStream getInputStream() {
		InputStream result = super.getInputStream();
		if (result == null) {
			result = rootDescriptor.getInputStream();
		}
		return result;
	}

	private static final Date nullDate = new Date(0);

	/**
	 * ファイルの日付を取得する。
	 * 
	 * @return ファイルの最終更新日付。ファイルが無い場合は「new Date(0)」を返す。
	 */
	@Override
	public Date getTimestamp() {
		Date result = super.getTimestamp();
		if (result.equals(nullDate)) {
			result = rootDescriptor.getTimestamp();
		}
		return result;
	}
}

 このようにラッパーにすることで、外側からは完全に隠蔽しつつ、内部で代替ロジックを動かすことができます。教科書的に言えばMyPageSourceDescriptor はSourceDescriptorをimplementsして、PaseSourceDescriptorをもう一つフィールドとして持ち、メソッドをそちらに移譲するようにした方が、構造がわかりやすいでしょう。しかし、それだとコード量が増えてしまうので、わかった上で継承で代用することはありだと思ってます。

 これを動かしたところ、うまくユーザごとのMayaaファイルが存在するときはそちらを、存在しない時は、ルートのMayaaファイルを処理するようになりました。

 また、この修正の副作用として、ユーザのテンプレートが存在しない場合に、ルートのテンプレートを探すようになりました。内部的にinsertしている場合も、うまいこと処理してくれるので、Mayaa内部で賢いことをしてくれているのだと思います。
 勘違いしてました。SourceDescrptorは、mayaaファイルだけでなく、htmlテンプレートも探しに行くようです。私としては大変都合が良いことでしたが、勘違いしないように気を付けた方が良さそうですね。