package org.itscool.weber.testcase;

import java.net.URL;
import java.util.LinkedList;

import org.itscool.commons.bean.BeanFactory;
import org.itscool.commons.connection.ConnectionManager;
import org.itscool.commons.connection.Transaction;
import org.itscool.commons.connection.TransactionUtil;
import org.itscool.commons.dao.EntityInfoMapping;
import org.itscool.weber.action.Action;
import org.itscool.weber.user.User;

/**
 * <p>
 * データベースの単体テスト機能を拡張したテストクラスです
 * </p>
 *
 * <p>
 * このクラスはorg.itscool.weber.testcase.WeberTestCaseクラスの基本機能を
 * 継承し、尚且つデータベース環境でのテスト機能を追加しています<br/>
 * </p>
 *
 * <p>
 * アクションクラスがデータベースにアクセスする処理を実装していた場合、
 * どのようなテストを行う必要があるのでしょうか？
 * </p>
 *
 * <p>
 * データ検索機能やデータ登録機能といったデータベースにアクセスするクラスを
 * テストする場合、テスト実行時に適切なテストデータがデータベースに登録され
 * ている必要があります。<br>
 * これが、テストの自動化を困難にする場合があります。
 * </p>
 *
 * <p>
 * テストデータをあらかじめDBに格納しておく場合では、ほとんどの場合を除いて
 * うまく行かないケースがあります。
 * </p>
 *
 * <p>
 * 検索のテストと同時に更新のテストや削除のテストを行った場合、
 * データベースの内容が変わってしまい、結果としてテスト結果も変わってしまいます。
 * </p>
 *
 * <p>
 * この問題を解決するにはテスト開始前にテーブルを初期化し、テストデータを作成
 * する必要があります。
 * </p>
 *
 * <p>
 * WeberDbTestCaseクラスはこのような処理を手助けしてくれます。
 * </p>
 *
 * <p>
 * WeberDbTestCaseクラスを使った単体テストを行うためには以下の準備が必要です<br/><br/>
 * <li>テスト用のデータベース及びテーブルが存在すること</li>
 * <li>テストデータを定義したXMLファイルが存在すること</li>
 * <li>ConnectionManagerクラスの設定がdi-config.xmlに登録されていること</li>
 * </p><br/>
 *
 * <p>
 * (1) ConnectionManagerクラスの登録<br/>
 * ConnectionManagerインターフェースはitscool-commons.jarに含まれている
 * データベース用のトランザクション管理クラスで、WeberDbTestCaseはこのクラスを
 * 使ってテスト用データベースの初期化やテストデータの作成を行います<br/>
 * ConnectionManagerの設定はJDBCとDataSourceのどちらを利用するかでdi-config.xml
 * の設定内容が変わります。
 * </p>
 *
 * <p>以下は、JDBCを利用する場合の設定例です</p>
 * <pre>
 * &lt;bean id="ConnectionManager" type="org.itscool.commons.connection.OriginalConnectionManager" singleton="true"&gt;
 *   &lt;property name="url" value="jdbc:hsqldb:hsql://localhost"/&gt;
 *   &lt;property name="user" value="sa"/&gt;
 *   &lt;property name="pwd" value=""/&gt;
 *   &lt;property name="drvname" value="org.hsqldb.jdbcDriver"/&gt;
 *   &lt;property name="timeout" value="300"/&gt;
 *   &lt;property name="initSize" value="3"/&gt;
 *   &lt;property name="maxSize" value="10"/&gt;
 *   &lt;property name="mode" value="false"/&gt;
 *   &lt;property name="showSql" value="false"/&gt;
 * &lt;/bean&gt;
 * </pre><br/>
 *
 * <p>以下は、DataSourceを利用する場合の設定例です</p>
 * <pre>
 * &lt;bean id="ConnectionManager" type="org.itscool.commons.connection.DataSourceManager" singleton="true"&gt;
 *   &lt;property name="dsName" value="java:comp/env/weber-jndi"/&gt;
 *   &lt;property name="mode" value="false"/&gt;
 *   &lt;property name="showSql" value="true"/&gt;
 * &lt;/bean&gt;
 * </pre>
 *
 * <p>
 * (2) テストデータ定義ファイルの作成<br/>
 * テストデータは以下の形式で記述したXMLファイルです<br>
 * </p>
 * <pre>
 * &lt;?xml version="1.0" encoding="Shift_JIS" ?&gt;
 *
 * &lt;dbdata-config&gt;
 *   &lt;table name="WEBER_ROLE"&gt;
 *       &lt;record id="1" name="'test_role'"/&gt;
 *       &lt;record id="2" name="'test_role2'"/&gt;
 *       &lt;record id="3" name="'test_role3'"/&gt;
 *   &lt;/table&gt;
 *   &lt;table name="WEBER_USER"&gt;
 *       &lt;record id="1" name="'test'" password="'test-pwd'" role_id="1"/&gt;
 *       &lt;record id="2" name="'test'" password="'test-pwd2'" role_id="2"/&gt;
 *       &lt;record id="3" name="'test'" password="'test-pwd3'" role_id="3"/&gt;
 *       &lt;record id="4" name="'best'" password="'test-pwd'" role_id="1"/&gt;
 *   &lt;/table&gt;
 *   &lt;table name="MESSAGE"&gt;
 *       &lt;record id="1" user_id="'ほげ'" subject="'hello'" contents="'テストメッセージです'" /&gt;
 *       &lt;record id="2" user_id="'太郎'" subject="'こんにちわ'" contents="'はじめまして'" /&gt;
 *       &lt;record id="3" user_id="'花子'" subject="'こんばんわ'" contents="'まいどです'" /&gt;
 *   &lt;/table&gt;
 * &lt;/dbdata-config&gt;
 * </pre>
 *
 * <p>
 * tableタグはテストテーブルの名前を定義します<br>
 * recoredタグはテストデータ1レコード分に該当します<br/>
 * recordタグの各属性は、テストテーブルの各種フィールドを指定し、属性値には
 * フィールドにセットする値を指定します<br>
 * セットする値が文字列型の場合は、設定値の両端をシングルクォーテーション(')で
 * くくります<br/>
 * </p><br/>
 *
 * <p><strong>
 * テストテーブル複数存在し、かつあるテストテーブルほかのテーブルを参照している
 * 場合は設定の順序に注意してください<br/>
 * たとえば上記の例で、WEBER_USERテーブルがWEBER_ROLEテーブルを参照している場合
 * WEBER_ROLEテーブルを上位に定義しなければなりません<br>
 * またMESSAGEテーブルがWEBER_USERテーブルを参照しているのであれば、
 * MESSAGEテーブルはWEBER_USERテーブルより下位に定義されていなければなりません<br>
 * </strong></p><br>
 *
 * <p>以下はテストクラスの実装例です</p>
 * <pre>
 * package test.org.itscool.msgbord;
 *
 * import org.itscool.msgbord.SaveMessageAction;
 * import org.itscool.webertest.testcase.WeberDbTestCase;
 *
 * import org.itscool.msgbord.Message;
 *
 * public class TestSaveMessageAction extends WeberDbTestCase{
 *     public void setUp(){
 *         this.initTables("test/org/itscool/msgbord/test-db-data.xml");
 *     }
 *
 *     public void testDoExecute(){
 *         SaveMessageAction action = new SaveMessageAction();
 *         Message message = new Message();
 *         message.setUserId("権兵衛");
 *         message.setSubject("aaa");
 *         message.setContents("bbb");
 *
 *         this.assertDoExecute(action, message, "success");
 *     }
 * }
 * </pre><br>
 *
 * <p>
 * this.initTables()メソッドは引数に指定されたテストデータ定義ファイルを
 * ロードし、テーブルのクリアとテストデータの登録を行います<br>
 * </p>
 *
 * <p>
 * 以下はテスト対象となるアクションクラスの例です
 * </p>
 * <pre>
 *
 * import java.util.List;
 *
 * import org.itscool.weber.action.Action;
 * import org.itscool.weber.action.ActionBucket;
 *
 * public class SaveMessageAction extends Action {
 *     public String doExecute(ActionBucket bucket){
 *         Message message = (Message)bucket.getForm();
 *         MessageDao dao = new MessageDao();
 *         dao.save(message);
 *
 *         List messages = (List)dao.getAllData();
 *         bucket.setAttribute("messages", messages);
 *         return "success";
 *     }
 * }
 * </pre><br>
 * <p>
 * WeberTestCaseを利用したテストを行う場合、別途JUnitクラスライブラリ
 * が必要です
 * </p>
 * <br>
 *
 * @author kano
 * @version 1.00A 2006/01/31 新規作成
 */
public abstract class WeberDbTestCase extends WeberTestCase{
    /** ConnectionManagerインスタンス */
    protected ConnectionManager mgr;
    /** テストデータ定義ファイルを格納したリストクラス */
    protected static LinkedList testDataList = new LinkedList();
    
    private static boolean initFlg = false;
    
    public WeberDbTestCase(){
        super();
        if(!initFlg){
            init();
            initFlg = true;
        }
    }
    
    /**
     * テスト全体を通して1回だけ実行される初期化関数
     */
    public void init(){
        try{
            ClassLoader parent = Thread.currentThread().getContextClassLoader();
            URL diconfigUrl = parent.getResource("di-config.xml");
            
            String configFwName;
            //System.out.println("di-config.xml path use = " + diconfigUrl.getPath());
            if(diconfigUrl == null ){
                String err = "di-config.xml path is null.";
                fail(err);
                log.error(err);
                return;
            }else{
                configFwName = diconfigUrl.getPath();
            }
            BeanFactory factory = BeanFactory.getInstance();
            factory.create(configFwName);
            mgr = (ConnectionManager) factory.createInstance("ConnectionManager");
            mgr.init();
            try{
                EntityInfoMapping daoMapping = (EntityInfoMapping)factory.createInstance("EntityInfoMapping");
            }catch(Exception ioe){
                log.info("DaoConfigReaderは実行されません");
            }
        }catch(Exception e){
            log.error(e.getMessage());
            e.printStackTrace();
            mgr.destroy();
            throw new WeberDbTestCaseInitException(e.getMessage());
        }
    }
    
    public void tearDown(){
        TransactionUtil.returnTransaction();
    }
    
    /**
     * テストデータを作成します
     * @param configName テストデータ定義ファイルのパス(アプリケーション
     * 実行ディレクトリからの相対パス）を指定します
     */
    public void initTables(String configName){
        //ClassLoader parent = ClassLoader.getSystemClassLoader();
        //ClassLoader parent = filterConfig.getClass().getClassLoader();
        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        URL configUrl = parent.getResource(configName);
        
        //DB定義情報ロード
        String path = configUrl.getPath();
        
        DbTestConfigReader dbReader = new DbTestConfigReader();
        try{
            testDataList.clear();
            dbReader.create(path, testDataList);
            //test.initTables();
            clearTables();
            insertDatas();
        }catch(Exception e){
            log.error(e.getMessage());
            throw new WeberDbTestCaseInitException(e.getMessage());
        }
        
    }
    
    /**
     * 全てのテストデータを削除します
     * @throws Exception
     */
    protected void clearTables(){
        Transaction transaction = TransactionUtil.getTransaction();
        if( testDataList.size() == 0 ) return;
        
        System.out.println("---- delete data start --- ");
        
        try{
            for(int i=testDataList.size(); i>0; i--){
                TableData table = (TableData)testDataList.get(i-1);
                StringBuffer sql = new StringBuffer("Delete From ");
                sql.append(table.getTableName());
                System.out.println(sql.toString());
                transaction.execute(sql.toString());
                TransactionUtil.commitTransaction();
            }
        }catch(Exception e){
            log.error(e.getMessage() + ", Execute role back.");
            TransactionUtil.rollbackTransaction();
            throw new WeberDbTestCaseInitException(e.getMessage());
        }finally{
            TransactionUtil.returnTransaction();
        }
        
        System.out.println("---- delete data end --- ");
        
    }
    
    /**
     * テストデータをデータベースに挿入します
     * @throws Exception
     */
    protected void insertDatas(){
        Transaction transaction = TransactionUtil.getTransaction();
        
        System.out.println("---- insert data start --- ");
        try{
            for(int i=0; i<testDataList.size(); i++){
                TableData table = (TableData)testDataList.get(i);
                for( int j=0; j<table.getRecordSize(); j++ ){
                    RecordData record = (RecordData)table.getRecordData(j);
                    StringBuffer sql = new StringBuffer("Insert Into ");
                    sql.append(table.getTableName());
                    sql.append(" ( ");
                    
                    //INSERT INTO テーブル名 (列名 , 列名 ,.......)
                    //   VALUES(データ , データ ,.......);
                    if(record.getFieldSize() == 0 ){
                        continue;
                    }
                    for(int k=0; k<record.getFieldSize(); k++){
                        FieldData field = (FieldData)record.getFieldData(k);
                        sql.append(field.getName());
                        if( (k+1)<record.getFieldSize()){
                            sql.append(", ");
                        }
                    }
                    sql.append(" ) Values ( ");
                    for(int k=0; k<record.getFieldSize(); k++){
                        FieldData field = (FieldData)record.getFieldData(k);
                        sql.append(field.getValue());
                        if( (k+1)<record.getFieldSize()){
                            sql.append(", ");
                        }
                    }
                    sql.append(" )");
                    System.out.println(sql.toString());
                    transaction.execute(sql.toString());
                }
            }
            
            TransactionUtil.commitTransaction();
        }catch(Exception e){
            log.error(e.getMessage() + ", Execute role back.");
            TransactionUtil.rollbackTransaction();
            throw new WeberDbTestCaseInitException(e.getMessage());
        }finally{
            TransactionUtil.returnTransaction();
        }
        System.out.println("---- insert data end --- ");
        
    }
    
    /**
     * アクションのdoExecuteメソッドを検証します。<BR>
     * データベースの更新が行われた場合はコミット/ロールバック制御を行います。
     * @param action Action アクションオブジェクト
     * @param form Form フォームオブジェクト
     * @param forward アクションの実行結果の予測値
     */
    public void assertDoExecute(Action action, Object form, String forward)throws Exception{
        try{
            super.assertDoExecute(action, form, forward);
        }catch(Exception e){
            throw e;
        }finally{
            int trnStatus = TransactionUtil.getTransaction().getTranStatus();
            if( trnStatus == Transaction.PLEASE_COMMIT ){
                TransactionUtil.commitTransaction();
            }else if( trnStatus == Transaction.PLEASE_ROLLBACK ){
                TransactionUtil.rollbackTransaction();
            }
        }
    }
    
    /**
     * アクションのcheckUserRoleメソッドを検証します。<BR>
     * データベースの更新が行われた場合はコミット/ロールバック制御を行います。
     * @param action Action アクションオブジェクト
     * @param user ユーザオブジェクト
     * @param ret ユーザチェック結果
     */
    public void assertCheckUserRole(Action action, User user, boolean ret){
        try{
            super.assertCheckUserRole(action, user, ret);
        }finally{
            int trnStatus = TransactionUtil.getTransaction().getTranStatus();
            if( trnStatus == Transaction.PLEASE_COMMIT ){
                TransactionUtil.commitTransaction();
            }else if( trnStatus == Transaction.PLEASE_ROLLBACK ){
                TransactionUtil.rollbackTransaction();
            }
        }
    }
    
    /**
     * アクションのvalidateメソッドを検証します。<BR>
     * データベースの更新が行われた場合はコミット/ロールバック制御を行います。
     * @param action Action アクションオブジェクト
     * @param forward アクションの実行結果の予測値
     */
    public void assertValidate(Action action, String forward)throws Exception{
        try{
            super.assertValidate(action, forward);
        }catch(Exception e){
            throw e;
        }finally{
            int trnStatus = TransactionUtil.getTransaction().getTranStatus();
            if( trnStatus == Transaction.PLEASE_COMMIT ){
                TransactionUtil.commitTransaction();
            }else if( trnStatus == Transaction.PLEASE_ROLLBACK ){
                TransactionUtil.rollbackTransaction();
            }
        }
    }
}
