ICEFacesでS2JDBCを使う

いずれはICEFacesを業務系システムの開発に使いたいので、データベース周りの連携を試してみました。個人的にSQLを書くスタイルが好みなので、その系では一番使いやすいS2JDBCを試しました。
まずはICEFacesからSeasarコンポーネントを呼べるようにします。連携用のツールはSeasarのプロダクト群を探してみても適当なものがなかったので、仕方がないので自作することにしました。この辺りはSpringの方が情報やツールが充実していますので、それを参考にしました。ICEFaces and Spring 2.5 in Java EE
org.springframework.web.jsf.DelegatingVariableResolverをコピーしてseasar用に書き換えます。

package web;

import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.VariableResolver;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

public class DelegatingVariableResolver extends VariableResolver {

	protected final Log logger = LogFactory.getLog(getClass());

	protected final VariableResolver originalVariableResolver;


	public DelegatingVariableResolver(VariableResolver originalVariableResolver) {
		this.originalVariableResolver = originalVariableResolver;
	}

	protected final VariableResolver getOriginalVariableResolver() {
		return this.originalVariableResolver;
	}


	public Object resolveVariable(FacesContext facesContext, String name) throws EvaluationException {
		Object value = resolveOriginal(facesContext, name);
		if (value != null) {
			return value;
		}
		Object bean = resolveSeasarBean(facesContext, name);
		if (bean != null) {
			return bean;
		}
		return null;
	}

	protected Object resolveOriginal(FacesContext facesContext, String name) {
		Object value = null;
        try{
            value = getOriginalVariableResolver().resolveVariable(facesContext, name);
        }catch(Exception e){
            e.printStackTrace();
        }
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Successfully resolved variable '" + name + "' via original VariableResolver");
		}
		return value;
	}

	protected Object resolveSeasarBean(FacesContext facesContext, String name) {
        return SingletonS2ContainerFactory.getContainer().getComponent(name);
	}
}

このクラスでやっていることは、ICEFacesコンポーネントを探すときに、まずICEFacesで管理しているコンポーネントを探しに行き、そこで見つからなかった場合にSeasarに探しに行くようにしているだけです。
これをfaces-config.xmlに追記します。

    <application>
        <view-handler>
            org.icefaces.netbeans.rave.web.ui.appbase.faces.ViewHandlerImpl
        </view-handler>
        <variable-resolver>web.DelegatingVariableResolver</variable-resolver>
    </application>

web.xmlにもSeasarの設定を追記します。

    <listener>
        <listener-class>org.seasar.framework.container.servlet.S2ContainerListener</listener-class>
    </listener>

    <filter>
        <filter-name>s2filter</filter-name>
        <filter-class>org.seasar.framework.container.filter.S2ContainerFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>s2filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

app.diconはこんな感じです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
        "http://www.seasar.org/dtd/components24.dtd">
<components>
    <include path="convention.dicon"/>
    <include path="aop.dicon"/>
    <include path="s2jdbc.dicon"/>
    <component name="personDao" class="dao.PersonDao"/>
    <component name="Page1" class="webapplication1.Page1" instance="request"/>
</components>

Page1コンポーネントはinstance="request"としています。(faces-config.xmlの設定と合わせた)
先ほどのweb.xmlでs2filterの設定を追加したのは、Seasarでrequestスコープのコンポーネント管理をするためです。

実は僕はここで大はまりしました。最初は、Page1の記述をfaces-config.xmlに、personDaoの記述をapp.diconにしていたのですが、それだと例外が発生します。どうやら、faces-config.xml内のコンポーネントはfaces-config.xml内のコンポーネントしかインジェクションできないようなのです。つまり、Seasarコンポーネントを使いたい場合は、PageクラスからSeasarで管理しなければいけないのです。ここが分からなくて、夜中の3時まではまってしまいました。
あと、NetBeansプラグインの問題?で、勝手にfaces-config.xmlにPage1のエントリを作ってしまうのです。一度消したはずなのに、知らずに復活していました。インジェクションされないので??としばらく悩みました。
Page1クラスはこんな感じです。

package webapplication1;

import com.icesoft.faces.component.jsfcl.data.DefaultSelectedData;
import com.sun.rave.faces.data.DefaultSelectItemsArray;
import com.sun.rave.web.ui.appbase.AbstractPageBean;
import dao.PersonDao;
import entity.NewClass;
import javax.faces.FacesException;

public class Page1 extends AbstractPageBean {

 〜中略〜

    public Page1() {

    }

    public void init() {
        super.init();
        try {
            _init();
        } catch (Exception e) {
            log("Page1 Initialization Failure", e);
            throw e instanceof FacesException ? (FacesException) e: new FacesException(e);
        }
    }

    public void preprocess() {
        name = "preprocess";
    }

    public void prerender() {
        name = "prerender";
        name = personDao.getPerson().name;
    }

    public void destroy() {
    }

    protected PersonDao personDao;

    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }

    protected String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

jdbc.diconはこんな感じです。データベースはNetBeansに付属しているサービスのTravelを使いました。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
	"http://www.seasar.org/dtd/components24.dtd">
<components namespace="jdbc">
	<include path="jta.dicon"/>
	<include path="jdbc-extension.dicon"
		condition="@org.seasar.framework.util.ResourceUtil@isExist('convention.dicon')"/>

	<component class="org.seasar.extension.jdbc.impl.BasicResultSetFactory"/>
	<component class="org.seasar.extension.jdbc.impl.ConfigurableStatementFactory">
		<arg>
			<component class="org.seasar.extension.jdbc.impl.BasicStatementFactory"/>
		</arg>
		<property name="fetchSize">100</property>
	</component>

	<!-- for Derby -->
	<component name="xaDataSource"
		class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
		<property name="driverClassName">
			"org.apache.derby.jdbc.EmbeddedDriver"
		</property>
		<property name="URL">
			"jdbc:derby://localhost:1527/travel"
		</property>
		<property name="user">"travel"</property>
		<property name="password">"travel"</property>
	</component>

	<component name="connectionPool"
		class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl">
		<property name="timeout">600</property>
		<property name="maxPoolSize">10</property>
		<property name="allowLocalTx">true</property>
		<destroyMethod name="close"/>
	</component>

	<component name="dataSource"
		class="org.seasar.extension.dbcp.impl.DataSourceImpl"
	/>
</components>

とりあえず、これで動きました。ICEFacesSeasarを連携させるもっといい方法を知っている方がいれば、教えてください。
今回の件ではSpringに感謝感謝です。