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>
<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>
とりあえず、これで動きました。ICEFacesとSeasarを連携させるもっといい方法を知っている方がいれば、教えてください。
今回の件ではSpringに感謝感謝です。