-TapestryとSeasarの連携

TapestryはWEB層に特化したフレームワークでセッションやCookieを利用して独自のページプロパティ永続化を行うDataSqueeze機構というものがあるのですが、本格的なアプリケーションのモデル層を負担するにはやはり相応の対応が必要です。Tapestryの付属サンプルではEJB(JBoss)が採用されていますし、HibernateほかのO/RマッピングTapestryのMLでも人気のようです。ここ近日はHiveMindやSpringFrameworkなどのIoCコンテナを持ってくることも注目されています。ここでは、Tapestryの後ろに、Seasar2のS2Containerを使う方法を説明したいと思います。単に持ってくるだけでなく、細工を施してより楽に開発作業が進められるようにしてみました。
まず、OGNL式でS2Containerから簡便にコンポーネントをルックアップできるように、組み込みのプロパティアクセッサを用意しておきます。これが後で楽するための細工です。OGNLの拡張は手数かけずともできますので、きちんとしておくと後の利用時にシンプルにコーディングができるようになります。SeasarにもSelという式言語がありますが、こちらはまだきっちり見てませんので語れず。

package org.seasar.contribute.tapestry;
import java.util.Map;
import org.seasar.framework.container.S2Container;
import ognl.OgnlException;
import ognl.PropertyAccessor;
public class S2ContainerPropertyAccessor implements PropertyAccessor {
  // S2用のコンポーネント取得メソッドOGNL版
  public Object getProperty(
       Map context, Object target, Object name) throws OgnlException {
    S2Container container = (S2Container) target;
    String componentName = name.toString();
    return container.getComponent(componentName);
  }
  public void setProperty(Map context, Object target, Object name,
    Object value) throws OgnlException {
    throw new UnsupportedOperationException();
  }
}

次に、Tapestryのエンジンクラスをカスタマイズします。リクエスト初期化プロセスの中でS2Containerを生成・参照を持ってくる機能を追加します。S2Containerは、ServletContextに保存されるので、WEBアプリケーション中に単一となります。

package org.seasar.contribute.tapestry;
import java.util.Map;
import javax.servlet.ServletContext;
import ognl.OgnlRuntime;
import org.apache.tapestry.engine.BaseEngine;
import org.apache.tapestry.request.RequestContext;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;
import org.seasar.framework.container.impl.S2ContainerImpl;
public class WithS2Engine extends BaseEngine {
  public static final String S2CONTAINER = "S2Container";
  public static final String CONFIG_PATH = "seasar_config";
  // OgnlRuntimeにS2用アクセッサを追加
  static {
    OgnlRuntime.setPropertyAccessor(
      S2Container.class, new S2ContainerPropertyAccessor());
  }
  protected void setupForRequest(RequestContext context) {
    super.setupForRequest(context);
    ServletContext sc = context.getServlet().getServletContext();
    Map map = (Map)getGlobal();
    S2Container container = (S2Container)map.get(S2CONTAINER);
    if(container == null) {
      // S2コンテナの生成・保存
      map.put(S2CONTAINER, createContainer());
    }
  }
  private S2Container createContainer() {
    // アプリケーションスペックXMLから設定を取得
    String path = getSpecification().getProperty(CONFIG_PATH);
    return S2ContainerFactory.create(path);
  }
}

さらには、ページオブジェクトのベースとなるクラスを作っておきます。

package org.seasar.contribute.tapestry;
import java.util.Map;
import org.apache.tapestry.html.BasePage;
import org.seasar.framework.container.S2Container;
public class WithS2Page extends BasePage {
  // S2Containerのゲッター
  public S2Container getS2Container() {
    Map map = (Map)getGlobal();
    return (S2Container)map.get(WithS2Engine.S2CONTAINER);
  }
}

これで、汎用のS2ContainerをTapestryに組み込むところは終わりです。以下はサンプル。ページのカウンター機能を作って見ましょう。まずはS2Container側です。

package sample.org.seasar.contribute.tapestry;
public interface Counter {
  public int getCount();
}

package sample.org.seasar.contribute.tapestry;
public class CounterImpl implements Counter {
  private int count;
  public int getCount() {
    return ++count;
  }
}

[seasar.xml]
<?xml version="1.0" encoding="Shift_JIS"?>
<components>
  <component name="counter"
    class="sample.org.seasar.contribute.tapestry.CounterImpl"/>
</components>

そして、Tapestry側。

[Home.html]
<html>
  <head>
    <title>Tapestry with Seasar2</title>
  </head>
  <body>
    <span jwcid="@Insert" value="ognl:s2Container.counter.count"/><br><br>
    <a href="#" jwcid="@PageLink" page="Home">reflesh</a>
  </body>
</html>

[Home.page]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification
  PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<page-specification class="org.seasar.contribute.tapestry.WithS2Page">
</page-specification>

[TapestryWithSeasar2.application]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE application
  PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<application name="TapestryWithSeasar2"
  engine-class="org.seasar.contribute.tapestry.WithS2Engine" >
  <property name="seasar_config" value="seasar.xml"/>
  <page name="Home" specification-path="Home.page"/>
</application>

CounterImplがシングルトンですから、複数アクセスでも無事にカウントアップしてくれます。Home.html中のOGNL式"s2Container.counter.count"は、WithS2Page#getS2Container().getComponent("counter").getCount();と処理してくれます。これは、はじめの「s2Container」はWithS2Pageで実装したメソッドを使い、「.counter」はS2ContainerPropertyAccessorのgetProperty()をOgnlRuntimeが呼び出します。そして、最後の「.count」はコンポーネントのメソッドです。