-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」はコンポーネントのメソッドです。