package org.itscool.commons.connection;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;

import org.itscool.commons.logging.AbstractLog;
import org.itscool.commons.logging.SimpleLog;

/**
 * Connectionインスタンスへのトランザクション機能を提供するクラスです<br/>
 *
 * <p>
 * 内部でjava.sql.Connectionインスタンスを保持しており、Transactionクラスの各
 * メソッドが実行されるときにこのインスタンスが使われます。<br/>
 * </p>
 *
 * @author KANO
 * @since jdk1.4.1
 * @version 1.00A 2004/11/1
 */
public class Transaction {
    protected AbstractLog log = SimpleLog.getInstance();
    /**
     * このインスタンスで、トランザクションは処理されていないことを表します
     */
    public static int NON_PROCESS = 0;
    /**
     * このインスタンスで、トランザクションが実施されかつコミット可能であること
     * を表します
     */
    public static int PLEASE_COMMIT = 1;
    /**
     * このインスタンスで、トランザクションが実施されたが失敗し、ロールバック
     * しなければならない状態であることを表しています
     */
    public static int PLEASE_ROLLBACK = 2;
    
    /**
     * このインスタンスの現在のステータス
     */
    protected int tranStatus = NON_PROCESS;
    /**
     * Connectionインスタンス
     */
    protected Connection connection = null;
    /**
     * トランザクション実行時のSQL表示モード
     */
    protected boolean showSql;
    
    /**
     * トランザクションの初期化を行います
     * @param con connectionインスタンス
     */
    public Transaction(Connection con){
        this.connection = con;
    }
    
    public void setShowSql(boolean showSql){
        this.showSql = showSql;
    }
    
    public boolean getShowSql(){
        return showSql;
    }
    
    /**
     * コミットを行います<BR>
     * @throws SQLException
     */
    public void commit() throws TransactionException{
        try{
            
            //トランザクションステータスを初期化
            this.setTranStatus(NON_PROCESS);
            
            // コミットします
            connection.commit();
        }catch(SQLException e){
            log.error(e.getMessage());
            throw new TransactionException(e);
        }
    }
    
    /**
     * ロールバック処理を行います
     * @throws SQLException
     */
    public void rollback() throws TransactionException{
        try{
            
            //トランザクションステータスを初期化
            this.setTranStatus(NON_PROCESS);
            
            connection.rollback();
        }catch(SQLException se){
            log.error(se.getMessage());
            throw new TransactionException(se);
        }
    }
    
    /**
     * Statement.executeUpdate(sql)を実行します
     * @param sql SQL
     * @throws SQLException
     */
    public int execute(String sql) throws TransactionException{
        Statement stmt = null;
        try{
            if(showSql){
                log.info(sql);
            }
            //ステートメントオブジェクトを生成
            stmt = connection.createStatement();
            //SQL実行
            int recNum = stmt.executeUpdate(sql);
            
            this.setTranStatus(PLEASE_COMMIT);
            
            return recNum;
        }catch(SQLException se){
            log.error(se.getMessage());
            this.setTranStatus(PLEASE_ROLLBACK);
            
            closeStatement(stmt);
            throw new TransactionException(se.getMessage(), sql);
            
        }finally{
            closeStatement(stmt);
        }
    }
    
    public Record selectOne(String sql) throws TransactionException{
        Records records = select(sql);
        if( records.size() > 0 ){
            return records.getRecord(0);
        }else{
            return null;
        }
    }
    
    /**
     * SELECTステートメントを発行します<BR>
     * 実行結果はLinkedListで返します<BR>
     * 又一つのレコードはHashMap形式となっており、フィールド名の別名（AS句で指定
     * した名前）がHashMapのキーとなります。
     * フィールドの別名が指定されていない場合はフィールド名がHashMapのキーとなり
     * ます（select句ではAS句を使ってフィールドの別名を使用することを推奨します）。
     * @param sql SQL
     * @return 実行結果をLinkedListで返します。
     * @throws SQLException
     */
    public Records select(String sql) throws TransactionException{
        Records result = new Records();
        Statement stmt = null;
        ResultSet rs = null;
        
        try{
            if(showSql){
                log.info(sql);
            }
            //ステートメントオブジェクトを生成
            stmt = connection.createStatement();
            //SQL実行
            rs = stmt.executeQuery(sql);
            
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            
            while( rs.next() ){
                Record record = new Record();
                //結果レコードの集合をHashMapで作成
                for(int i=0;i<columnCount;i++){
                    String colName = metaData.getColumnName(i+1);
                    String colAsName = metaData.getColumnLabel(i+1);
                    
                    //列名に別名が指定されている場合は別名を使用する
                    //集計関数の場合は必ず別名が指定するべきである
                    if(!colAsName.equals("")){
                        colName = colAsName;
                    }
                    String value = rs.getString(colName);    //こちらを使うこと
//                    //Object value = rs.getObject(colName);    //Bean.setPropertyで型の特定ができない
//                    System.out.print(colName);
//                    System.out.print(":" + value + " ");
                    
                    //modify 2005/10/12 kanoum start
//                    record.put(colName, value);
                    //SQL実行時のカラム小文字化対応
                    record.put(colName.toUpperCase(), value);
                    //modify 2005/10/12 kanoum end
                }
                result.add(record);
//                System.out.println("");
            }
        }catch(SQLException se){
            closeResultSet(rs);
            closeStatement(stmt);
            throw new TransactionException(se.getMessage(), sql);
        }finally{
            closeResultSet(rs);
            closeStatement(stmt);
        }
        return result;
    }
    
    /**
     * コネクションを閉じます
     * @throws SQLException
     */
    public void close() throws TransactionException{
        try{
            // コミットします
            connection.close();
        }catch(SQLException e){
            log.error(e.getMessage());
            throw new TransactionException(e);
        }
    }
    
    public boolean isClosed() throws TransactionException{
        boolean ret = true;
        try{
            if( connection != null ){
                ret = connection.isClosed();
            }
        }catch(SQLException e){
            log.error(e.getMessage());
            throw new TransactionException(e);
        }
        return ret;
    }
    
    /**
     * Connectionインスタンスを取得します（非推奨）
     * @return Connectionインスタンスを取得します（非推奨）
     */
    public Connection getConnection() {
        return connection;
    }
    
    /**
     * PreparedStatementを取得します
     * @param sql SQL文字列
     * @return PreparedStatementを返します
     */
    public PreparedStatement getPrepareStatement(String sql){
        try{
            PreparedStatement pstmt = connection.prepareStatement(sql);
            return pstmt;
        }catch(SQLException e){
            log.error(e.getMessage());
            throw new TransactionException(e);
        }
    }
//
//    /**
//     * Connectionインスタンスをセットします（非推奨）
//     * @param connection Connectionインスタンスをセットします
//     */
//    public void setConnection(Connection connection) {
//        this.connection = connection;
//    }
    
    /**
     * Statementインスタンスを終了します
     * @param stmt Statementインスタンス
     * @throws TransactionException 終了処理に失敗した場合
     */
    protected void closeStatement(Statement stmt)
    throws TransactionException{
        try{
            if( stmt != null ){
                stmt.close();
                stmt = null;
            }
        }catch(SQLException se){
            log.error(se.getMessage());
            throw new TransactionException(se);
        }
    }
    
    /**
     * ResultSetインスタンスを終了します
     * @param rs ResultSetインスタンス
     * @throws TransactionException 終了処理に失敗した場合
     */
    protected void closeResultSet(ResultSet rs)
    throws TransactionException{
        try{
            if( rs != null ){
                rs.close();
                rs = null;
            }
        }catch(SQLException se){
            throw new TransactionException(se);
        }
    }
    
    /**
     * このインスタンスの現在のステータスを取得します
     * @return 現在のステータスを返します
     */
    public int getTranStatus() {
        return tranStatus;
    }
    
    /**
     * このインスタンスのステータスを更新します
     * @param tranStatus ステータス
     */
    public void setTranStatus(int tranStatus) {
        this.tranStatus = tranStatus;
    }
}
