/* $Id: ScriptingMessageAdapter.java,v 1.7 2007/12/04 09:16:10 nito Exp $
 *
 * Copyright (c)ARGO 21, Corporation. 2005, 2006.  All rights reserved.
 * 
 * This file is part of Nautica Workflow Core.
 * 
 *  Nautica Workflow Core is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2.1 of the License, or
 *  (at your option) any later version.
 * 
 *  Nautica Workflow Core is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 * 
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Nautica Workflow Core; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *  
 */
package jp.co.argo21.nautica.workflow.ta;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

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

import jp.co.argo21.nautica.workflow.dataaccess.AppExecutionBean;
import jp.co.argo21.nautica.workflow.engine.DataAccessManager;
import jp.co.argo21.nautica.workflow.engine.SystemChecker;
import jp.co.argo21.nautica.workflow.jms.WorkflowMessage;
import jp.co.argo21.nautica.workflow.script.ScriptEngineBuilder;
import jp.co.argo21.nautica.workflow.util.StringManager;

/**
 * スクリプト実行用クラス
 * 
 * @author kiyoda(Argo 21, Corp.)
 * @version $Revision: 1.7 $
 * @since Nautica Workflow 1.0
 */
public class ScriptingMessageAdapter extends AbstractToolAgentMessageAdapter
{

	/**
	 * デフォルトコンストラクタ
	 */
	ScriptingMessageAdapter()
	{
		super();
	}

	@Override
	protected WorkflowMessage getMessage(HashMap<String, Serializable> map)
	{
		return new ScriptingExecutionInfo(map);
	}

	/**
	 * スクリプトを実行する。
	 * 
	 * アプリケーション実行情報に従って、スクリプトを実行する。 実行状況に合わせて、随時、アプリケーション実行テーブル(APPEXEC)を 更新する。
	 * 
	 * @param mid
	 *            メッセージID
	 * @param message
	 *            メッセージ
	 * @param isRedelivered
	 *            再配信フラグ
	 * @see jp.co.argo21.nautica.workflow.jms.WorkflowMessageAdapter#onMessage(java.lang.String,
	 *      jp.co.argo21.nautica.workflow.jms.WorkflowMessage, boolean)
	 */
	public void onMessage(String mid, WorkflowMessage message,
			boolean isRedelivered)
	{
		if (!ScriptingExecutionInfo.class.isAssignableFrom(message.getClass())) {
			return;
		}
		ScriptingExecutionInfo info = (ScriptingExecutionInfo) message;

		if (isRedelivered) {
			//再配信されたメッセージの場合
			ExecutionCounter.update(mid);
		} else {
			//初めて受信したメッセージの場合
			ExecutionCounter.set(mid, info.getRetryCount());
		}

		AppExecutionBean bean = null;
		AbstractToolAgent agent = null;
		ApplicationState astate = ApplicationState.NOT_STARTED;
		try {
			// スクリプト実行の設定情報を取得する
			ScriptingConfig config = ScriptingConfigFactory.getConfig();
			agent = (AbstractToolAgent) (new ScriptingToolAgentFactory())
					.getToolAgent();

			log.info(StringManager.get("I2011"));

			DataAccessManager.begin(false);
			bean = getApplicationState(info);
			astate = ApplicationState.RUNNING;
			updateState(info, bean.getAppState(), astate);
			// スクリプトを実行する
			int result = execScript(config, info);
			// ログ出力
			log.info(StringManager.get("I2012"));

			// アプリケーションの実行結果に応じて、
			// アプリケーション実行テーブルと作業項目を更新する。
			finalizeApplication(info, astate, result, agent);

			ExecutionCounter.clear(mid);
			
			DataAccessManager.commit();
		} catch (Exception ex) {
			// アプリケーション実行
			int count = ExecutionCounter.get(mid);

			// ログ出力
			String errMsg = StringManager.get("E2011")
				+ ": (Retry Count = " + (count - 1);
			log.error(errMsg, ex);

			if (count > 1) {
				// まだリトライ回数が残っている場合
				try {
					DataAccessManager.rollback();
				} catch (Exception ex2) {
					/* Ignore */
				}
				throw new RuntimeException(ex);
			} else {
				try {
					// アプリケーション実行テーブル(APPEXEC)テーブルの
					// アプリケーションの状態を変更する
					finalizeApplication(info, astate, -1, agent);

					// コミット
					DataAccessManager.commit();
				} catch (Exception ex2) {
					// ログ出力
					String errMsg2 = StringManager.get("E2012");
					log.error(errMsg2, ex);
				}	
			}
		} finally {
			try {
				DataAccessManager.close();
			} catch (Exception ex2) {
				/* Ignore */
			}
		}
	}

	/**
	 * スクリプトを実行する
	 * 
	 * @param config
	 *            スクリプト設定情報
	 * @param info
	 *            スクリプト実行情報
	 * @return 実行結果
	 * @throws ScriptException
	 * @throws FileNotFoundException
	 */
	private int execScript(ScriptingConfig config, ScriptingExecutionInfo info)
			throws ScriptException, FileNotFoundException
	{

		int result = 0;
		String appName = info.getAppName();
		String wid = info.getWid();

		// エンジンを取得
		ScriptEngineBuilder builder = ScriptEngineBuilder.getInstance();

		ScriptEngine engine = builder.getEngineByName(config
				.getEngineName(appName));

		// スクリプトへのパラメータを設定
		Bindings bindings = engine.createBindings();
		engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);

		// 作業項目IDをスクリプトに引き渡す。
		bindings.put(ScriptingExecutionInfo.NAUTICA_WID, wid);

		Map<String, Object> params = info.getParameters();
		for (String key : params.keySet()) {
			Object value = params.get(key);
			bindings.put(key, value);
		}

		if (config.getType(appName).equals("command")) {
			// スクリプトコマンドを実行する
			engine.eval(config.getCommand(appName), bindings);
		} else {
			// スクリプトファイルを実行する
			String filename = getScriptingPath(config, appName);
			String shortFilename = filename.replaceAll(".*[^A-Za-z0-9_\\.]", "");
			bindings.put(ScriptEngine.FILENAME, shortFilename);
			engine.eval(new FileReader(filename));
		}

		return result;
	}

	/**
	 * スクリプトファイルのパスを返す。
	 * 
	 * @param config
	 *            スクリプト設定情報
	 * @param appName
	 *            アプリケーション名
	 * @return スクリプトファイルのパス
	 */
	private String getScriptingPath(ScriptingConfig config, String appName)
	{

		String separator = SystemChecker.getProperty("file.separator");
		String filename = config.getFilename(appName);
		File file = new File(filename);

		// 絶対パスならそのまま返す。
		if (file.isAbsolute())
			return filename;

		// それ以外は、頭にローカルパスを補完して返す。
		return ScriptingToolAgent.LOCAL_SCRIPT_PATH + separator
				+ config.getEngineName(appName) + separator + filename;
	}
}
