yojikのlog

yojikのブログです

ClickEJBServlet

案件の隙間ということもありヒマしているので、WebフレームワークClickを勉強中。そこでClickをView層にしてEJB3のEntityBeanを気軽に扱う仕組みが欲しくなった。永続化層を扱わないとWebフレームワークは意味が無いし、どうせならEJB3の勉強もしておこうというわけです。この場合MVCとか面倒くさいので、Document-Viewみたいな感じでWebアプリを作りたい。
...というわけで、ClickServlet側でTransaction等の管理をさせる仕組みをつくってみた。ついでにPageクラスにPersistenceContextをインジェクトしてる(アノテーションで当てるだけの簡易DIですが)。本当はSeasar2とかSpringでごにょごにょするのがいいんだろうけど*1を、この件では細かい問題にぶつかりそうな予感がしたので、車輪の再発明をしてしまった。
実際にこの基盤の上でサンプルの実装をやってみたら、予想通り快適だった。RailsのチュートリアルとかにありそうなTodoアプリのサンプルなら、Pageクラス、Pageテンプレート、Entityの3クラスでつくれる。感覚的にはStruts+諸々で同じことやった場合の数十倍は楽。
微妙に癖はある気がするんだけど、Clickはいいねぇ。フレームワークの層が薄くていじり易い。多少のトラブルにぶつかっても立ち向かっていけそうな感じ。

package yojik.sample.fw;
import java.lang.reflect.Field;
import java.util.Map;
import net.sf.click.util.ErrorPage;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.naming.*;
import javax.transaction.*;
import static javax.transaction.Status.*;
import javax.persistence.*;
import net.sf.click.*;
import java.lang.reflect.*;
import java.lang.annotation.*;

public class ClickEJBServlet extends ClickServlet {
    protected void processPage(final Page page) throws Exception {
        if(page instanceof ErrorPage) {
            super.processPage(page);
            return;
        }
        String persistenceUnit =getInitParameter("persistenceUnit");
        TransactionalProcess tp = new TransactionalProcess(persistenceUnit) {
            public void execute() throws Exception {
                inject(page, PersistenceContext.class, this.em);
                ClickEJBServlet.super.processPage(page);
            }
        };
        if(tp!=null) tp.doit();
    }
    static  void inject(Page target, Class<? extends Annotation> annon ,Object resource) {
        try {
            Field[] fields = target.getClass().getDeclaredFields();
            for(Field f: fields) {
                if(f.getAnnotation(annon)!=null) {
                    f.set(target, resource);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class TransactionalProcess {
    UserTransaction tx;
    EntityManagerFactory emf;
    EntityManager em;
    String persistenceUnit;
    boolean skipTran = false;
    public void execute() throws Exception{}
    public TransactionalProcess(String persistenceUnit) {
        this.persistenceUnit = persistenceUnit;
    }
    public void doit() throws Exception {        
        try {
            begin();
            execute();
            commit();
        } catch (Exception e) {
            rollback();
            throw e;
        }
    }
    void begin() throws Exception {
        InitialContext ic = new InitialContext();
        this.tx = (UserTransaction)ic.lookup("java:comp/UserTransaction");
        if(tx.getStatus() == STATUS_NO_TRANSACTION) {
            tx.begin();
        } else {
            skipTran = true;
        }
        this.emf =(EntityManagerFactory)ic.lookup("java:EntityManagerFactories/" + persistenceUnit);
        this.em  = emf.createEntityManager();
    }    
    void rollback() {
        try {
            if(tx==null || skipTran) return;
            tx.rollback();
            if(em!=null) em.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    void commit() {
        try {
            if(tx==null || skipTran) return;
            tx.commit();
            if(em!=null) em.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

↑面倒になってExceptionをptintStackTraceしたりしてるので注意。いつかまとまったものが出来ればどこかにおきます。
余談だけどこういう匿名クラスの使い方がすごく好き。ある意味クロージャ以上に便利じゃない?

追記:

あーこんな感じの設定ファイルが必要です。(JBoss4CR2用)
web.xml

 <servlet>
  <servlet-name>click-servlet</servlet-name>
  <servlet-class>yojik.sample.fw.ClickEJBServlet</servlet-class>
  <load-on-startup>0</load-on-startup>
  <init-param>
    <param-name>persistenceUnit</param-name>
    <param-value>yojiksample</param-value>
  </init-param>
 </servlet>

persistence.xml

<persistence>
  <persistence-unit name="yojiksample">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:/yojiksample</jta-data-source>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
      <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
      <property name="hibernate.transaction.flush_before_completion" value="true"/>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      <property name="hibernate.show_sql" value="true"/>
      <!-- add for servlet -->
      <property name="jboss.entity.manager.factory.jndi.name"  value="java:EntityManagerFactories/yojiksample"/>
      <property name="jboss.entity.manager.jndi.name"  value="java:EntityManagers/yojiksample"/>  
    </properties>
  </persistence-unit>
</persistence>

長いエントリになってしまった。

*1:Springは一応本家にあるし、S2はたけぞうさんが[http://www3.vis.ne.jp/~asaki/p_diary/diary.cgi?Date=2006-03-03#2006030300:title=こちら]でやってますね