トップ «前の日記(2009-11-18) 最新 次の日記(2009-11-23)» 編集

Ussy Diary


2009-11-19

[Java][Seasar]mayaa で静的なリソースにタイムスタンプをつける InjectionResolver

クライアントからのアクセスを減らすようにするために、リソースファイルにパラメータをつける手法は有名だと思います。とはいえ HTML を返す View にちまちまタイムスタンプを埋め込む作業はしたくないですし、漏れも出てしまうなあと思っていたところ、 mayaa でそれなりにうまく記述できましたのでコードを晒してみます。ただ動的に出力するもの(mayaa ファイル)に関しては、個別に対応する必要があります。それでもだいぶ楽になるかなと思います。

package example.support.mayaa;

import java.io.File;
import java.util.regex.Pattern;

import org.seasar.mayaa.FactoryFactory;
import org.seasar.mayaa.builder.injection.InjectionChain;
import org.seasar.mayaa.builder.injection.InjectionResolver;
import org.seasar.mayaa.engine.specification.NodeAttribute;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.SpecificationNode;
import org.seasar.mayaa.impl.CONST_IMPL;
import org.seasar.mayaa.impl.ParameterAwareImpl;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;

/**
 * リソースファイルのタイムスタンプをクエリーパラメーターにつける {@link InjectionResolver} 拡張クラスです。
 *
 * <p>
 * img, script, css 要素に対して実行されますが、 mayaa
 * ファイルから動的に生成するものには付加しませんので、それぞれ個別に対応する必要があります。
 * </p>
 *
 * <ul>
 * <li>&lt;script type="text/javascript"
 * src="/js/jquery.js?1258000000000"&gt;&lt;/script&gt;</li>
 * <li>&lt;link type="text/css" href="/css/default.css?1258000000000" /&gt;
 * <li>&lt;img src="/img/logo.png?1258000000000" /&gt;
 * </ul>
 *
 * @author ussy
 */
public class ResourceTimestampSetter extends ParameterAwareImpl implements InjectionResolver, CONST_IMPL {

	private static final long serialVersionUID = 1L;

	private static final QName QH_IMG = SpecificationUtil.createQName(URI_HTML, "img");

	private static final QName QH_SCRIPT = SpecificationUtil.createQName(URI_HTML, "script");

	private static final QName QH_LINK = SpecificationUtil.createQName(URI_HTML, "link");

	private static final QName QH_SRC = SpecificationUtil.createQName(URI_HTML, "src");

	private static final QName QH_HREF = SpecificationUtil.createQName(URI_HTML, "href");

	private static final QName QX_IMG = SpecificationUtil.createQName(URI_XHTML, "img");

	private static final QName QX_SCRIPT = SpecificationUtil.createQName(URI_XHTML, "script");

	private static final QName QX_LINK = SpecificationUtil.createQName(URI_XHTML, "link");

	private static final QName QX_SRC = SpecificationUtil.createQName(URI_XHTML, "src");

	private static final QName QX_HREF = SpecificationUtil.createQName(URI_XHTML, "href");

	/**
	 * リソースファイルへのパスにタイムスタンプを追加します。
	 *
	 * <p>
	 * ファイルが取得できない場合は何も行いません。
	 * </p>
	 *
	 * @param original
	 *            {@link SpecificationNode}
	 * @param srcName
	 *            ファイル参照パス
	 */
	protected void appendQueryTimestampParameter(SpecificationNode original, QName srcName) {
		NodeAttribute src = original.getAttribute(srcName);
		if (src == null) {
			return;
		}

		PathAdjuster adjuster = ProviderUtil.getPathAdjuster();
		boolean needAdjust = adjuster.isTargetNode(original.getQName());
		if (!(needAdjust && adjuster.isTargetAttribute(original.getQName(), srcName))) {
			return;
		}

		String basePath = EngineUtil.getSourcePath(original);
		String value = adjuster.adjustRelativePath(basePath, src.getValue());
		String filePath = FactoryFactory.getApplicationScope().getRealPath(value);
		File resouruce = new File(filePath);
		if (resouruce.exists()) {
			String timestamp = String.valueOf(resouruce.lastModified());
			original.removeAttribute(srcName);
			original.addAttribute(srcName, src.getValue() + "?" + timestamp);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public SpecificationNode getNode(SpecificationNode original, InjectionChain chain) {
		if (original == null || chain == null) {
			throw new IllegalArgumentException();
		}

		QName originalName = original.getQName();
		if (QH_SCRIPT.equals(originalName)) {
			appendQueryTimestampParameter(original, QH_SRC);
		} else if (QX_SCRIPT.equals(originalName)) {
			appendQueryTimestampParameter(original, QX_SRC);
		} else if (QH_LINK.equals(originalName)) {
			appendQueryTimestampParameter(original, QH_HREF);
		} else if (QX_LINK.equals(originalName)) {
			appendQueryTimestampParameter(original, QX_HREF);
		} else if (QH_IMG.equals(originalName)) {
			appendQueryTimestampParameter(original, QH_SRC);
		} else if (QX_IMG.equals(originalName)) {
			appendQueryTimestampParameter(original, QX_SRC);
		}

		return chain.getNode(original);
	}

}

META-INF/org.seasar.mayaa.provider.ServiceProvier に InjectionResolver を追加します。

<resolver class="example.support.mayaa.ResourceTimestampSetter" />

これで Servlet からのレスポンスを見てみると、静的に埋め込んだリソースファイルすべてにファイルのタイムスタンプが、クエリ文字列として追加されます。

変換前

<img src="./../img/logo.png" />

変換後

<img src="/img/logo.png?1258000000000" />

mayaa は静的な部分に関しては一度だけコンパイルして処理をしてくれるので、自動ビルドなり一度アクセスするとき以外は、この処理は行いません。ブラウザがキャッシュをみてくれるようになることを考えると、十分な見返りがありそうです。

実際の業務ではタイムスタンプを埋め込むものは使用していないのですが、これはこれで、なかなか使えそうな気がしました。