package org.maachang.comet.httpd.engine.script;

import java.util.HashMap;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.ServiceDef;
import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdParams;
import org.maachang.comet.httpd.HttpdRequest;
import org.maachang.comet.httpd.HttpdStateException;
import org.maachang.comet.httpd.engine.script.cache.CacheScriptManager;
import org.maachang.comet.httpd.engine.session.HttpdSession;
import org.maachang.manager.GlobalManager;
import org.maachang.util.FileUtil;

/**
 * リクエストに対する実行用ターゲットスクリプト.
 * 
 * @version 2007/08/24
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class TargetScript {
	
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( TargetScript.class ) ;
    
    /**
     * スクリプトコンテキスト.
     */
    private ScriptContext context = null ;
    
    /**
     * ターゲットスクリプト実行用.
     */
    private Script script = null ;
    
    /**
     * リクエストヘッダ.
     */
    private HttpdRequest request = null ;
    
    /**
     * アクセスパス.
     */
    private String path = null ;
    
    /**
     * アクセス名.
     */
    private String accessName = null ;
    
    /**
     * 処理結果.
     */
    private Object result = null ;
    
    /**
     * アクセスタイム.
     */
    private long accessTime = -1L ;
    
    /**
     * コンストラクタ.
     */
    private TargetScript() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 条件を指定してスクリプトを生成します.
     * <BR>
     * @param path 対象のパス名を設定します.
     * @param request 対象のリクエスト情報を設定します.
     * @exception Exception 例外.
     */
    public TargetScript( String path,HttpdRequest request )
        throws Exception {
        if( request == null ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP10_400,"不正なリクエストを受信しました" ) ;
        }
        this.accessTime = System.currentTimeMillis() ;
        WebAppScriptFactory factory = getFactory() ;
        // 読み込み対象の拡張子が[.ms]及び、拡張子なし以外の場合.
        path = path.trim() ;
        String name = FileUtil.getFileName( path ) ;
        path = path.substring( 0,path.length()-name.length() ) ;
        if( name.indexOf( "." ) != -1 ) {
            if( name.endsWith( ScriptDef.SCRIPT_PLUS ) == true ) {
                name = name.substring( 0,name.length()-ScriptDef.SCRIPT_PLUS.length() ) ;
            }
        }
        //if( name.endsWith( ScriptDef.SCRIPT_BY_CONTROLLER ) ) {
        //    name = name.substring( 0,name.length()-ScriptDef.SCRIPT_BY_CONTROLLER.length() ) ;
        //}
        // ターゲットのスクリプトエンジンを取得.
        Script script = factory.getApplication( path+name ) ;
        if( script == null ) {
            throw new HttpdStateException( HttpdErrorDef.HTTP11_500,
                "対象パス["+(path+name)+"]スクリプトの読み込みに失敗しました" ) ;
        }
        // 各条件を設定.
        this.context = null ;
        this.script = script ;
        this.request = request ;
        this.path = path ;
        this.accessName = path+name ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        accessTime = -1L ;
        context = null ;
        script = null ;
        request = null ;
        path = null ;
        result = null ;
        accessName = null ;
    }
    
    /**
     * スクリプト実行.
     * <BR><BR>
     * スクリプトを実行します.
     * <BR>
     * @param params 対象のパラメータを設定します.
     * @return Object スクリプト処理結果が返されます.
     * @exception Exception 例外.
     */
    public Object execution( HashMap<String,Object> params ) throws Exception {
        Object ret = null ;
        BaseModel baseModel = new BaseModelImpl() ;
        if( params == null ) {
            params = new HashMap<String,Object>() ;
        }
        params.put( ScriptDef.SCRIPT_MODE,ScriptDef.MODE_NOT_COMET ) ;
        try {
            this.context = new SimpleScriptContext() ;
            // ライブラリスクリプトを読み込む.
            CacheScriptManager.getInstance().script( this.context ) ;
            // 各パラメータを取得.
            createContext( this.context,baseModel ) ;
            // デフォルトパラメータを取得.
            ScriptDef.setDirectoryByBindings( this.context ) ;
            // ターゲットスクリプトを読み込む.
            ret = this.script.getScript().execution( this.context,params ) ;
            if( baseModel.isCreate() == true ) {
                baseModel.commit() ;
            }
        } catch( ScriptException sc ) {
            if( EndScript.isEndScript( sc ) == true ) {
                ret = EndScript.getEndByResult( this.context ) ;
                if( baseModel.isCreate() == true ) {
                    baseModel.commit() ;
                }
            }
            else {
                if( baseModel.isCreate() == true ) {
                    try {
                        baseModel.rollback() ;
                    } catch( Exception ee ) {
                    }
                }
                throw new BaseScriptException( sc ) ;
            }
        } catch( Exception e ) {
            if( baseModel.isCreate() == true ) {
                try {
                    baseModel.rollback() ;
                } catch( Exception ee ) {
                }
            }
            throw e ;
        } finally {
            if( baseModel.isCreate() == true ) {
                try {
                    baseModel.getRecord().close() ;
                } catch( Exception ee ) {
                }
            }
            try {
                CacheScriptManager.getInstance().executionByExitRequest() ;
            } catch( Exception ee ) {
            }
            baseModel = null ;
            if( LOG.isDebugEnabled() ) {
                LOG.debug( "... readScript:" + accessName +
                    " - ["+( System.currentTimeMillis()-accessTime )+"ms]" ) ;
            }
        }
        result = ret ;
        return ret ;
    }
    
    /**
     * スクリプトコンテキストを取得.
     * <BR><BR>
     * スクリプトコンテキストを取得します.
     * <BR>
     * @return ScriptContext スクリプトコンテキストが返されます.
     */
    public ScriptContext getScriptContext() {
        return context ;
    }
    
    /**
     * セッションオブジェクトを取得.
     * <BR><BR>
     * セッションオブジェクトを取得します.
     * <BR>
     * @return HttpdSession セッションオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public HttpdSession getSession()
        throws Exception {
        return request.getSession() ;
    }
    
    /**
     * アクセスパスを取得.
     * <BR><BR>
     * アクセスパスを取得します.
     * <BR>
     * @return String アクセスパスが返されます.
     */
    public String getPath() {
        return path ;
    }
    
    /**
     * 処理結果を取得.
     * <BR><BR>
     * 処理結果を取得します.
     * <BR>
     * @return Object 処理結果が返されます.
     */
    public Object getResult() {
        return result ;
    }
    
    /**
     * コンテキストを生成.
     */
    private void createContext( ScriptContext ctx,BaseModel baseModel )
        throws Exception {
        // 基本パラメータを定義.
        Bindings bindings = ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ;
        bindings.put( ScriptDef.SCRIPT_BY_MODEL,baseModel ) ;
        bindings.put( ScriptDef.SCRIPT_BY_PATH,path ) ;
        bindings.put( ScriptDef.SCRIPT_BY_HEADER,this.request.getHeader() ) ;
        HttpdSession session = request.getSession() ;
        if( session != null ) {
            bindings.put( ScriptDef.SCRIPT_BY_SESSION,session ) ;
        }
        // リクエスト内容を設定.
        pushRequestParams( bindings,request.getQuery() ) ;
    }
    
    /**
     * WebAppScriptFactoryを取得.
     */
    private static final WebAppScriptFactory getFactory() {
        return ( WebAppScriptFactory )GlobalManager.getValue(
            ServiceDef.MANAGER_BY_WEB_APP_FACTORY ) ;
    }
    
    /**
     * リクエストパラメータを設定.
     */
    private static final void pushRequestParams( Bindings bindings,HttpdParams params ) {
        HashMap<String,Object> spms =ScriptDef.getQuery( params ) ;
        bindings.put( ScriptDef.SCRIPT_BY_QUERY,spms ) ;
    }
}
