/* $Id: AbstractToolAgent.java,v 1.4 2007/11/28 07:09:06 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.sql.Timestamp;
import java.util.List;

import org.apache.log4j.Logger;

import jp.co.argo21.nautica.workflow.dataaccess.AppExecutionBean;
import jp.co.argo21.nautica.workflow.dataaccess.AppExecutionDAO;
import jp.co.argo21.nautica.workflow.dataaccess.WorkflowDAOFactory;
import jp.co.argo21.nautica.workflow.engine.DataAccessManager;
import jp.co.argo21.nautica.workflow.engine.MessageTransporter;
import jp.co.argo21.nautica.workflow.engine.SystemChecker;
import jp.co.argo21.nautica.workflow.engine.WorkflowEngine;
import jp.co.argo21.nautica.workflow.engine.WorkflowEngineConstants;
import jp.co.argo21.nautica.workflow.jms.MessagingException;
import jp.co.argo21.nautica.workflow.util.StringManager;
import jp.co.argo21.nautica.workflow.wfmc.ApplicationBusyException;
import jp.co.argo21.nautica.workflow.wfmc.ApplicationNotDefinedException;
import jp.co.argo21.nautica.workflow.wfmc.ApplicationNotStartedException;
import jp.co.argo21.nautica.workflow.wfmc.Attribute;
import jp.co.argo21.nautica.workflow.wfmc.ConnectionFailedException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidSessionException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidToolAgentException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidWorkItemException;
import jp.co.argo21.nautica.workflow.wfmc.ToolAgent;

/**
 * ツールエージェントの抽象クラス。
 * 
 * ツールエージェントハンドル（＝実行ID）の取得、 アプリケーション実行テーブルへのレコード追加、 キューへの実行情報の送信、 プロセスの管理 を行う。
 * 
 * @author kiyoda(Argo 21, Corp.)
 * @version $Revision: 1.4 $
 * @since Nautica Workflow 1.0
 */
public abstract class AbstractToolAgent implements ToolAgent
{

	/** ツールエージェントパス */
	public static final String TOOLAGENT_PATH;

	/* ログオブジェクト */
	protected Logger log = Logger.getLogger(this.getClass());

	/* メッセージ配送者 */
	protected MessageTransporter tp = null;

	/* アプリケーション試行回数 */
	protected int retryCount = 1;

	/** ユーザID */
	protected String userId = null;

	/** パスワード */
	protected String passwd = null;

	/** セッションID */
	protected String session = null;

	static {
		String home = SystemChecker
				.getProperty(WorkflowEngineConstants.NAUTICA_HOME);
		String separator = SystemChecker.getProperty("file.separator");
		TOOLAGENT_PATH = home + separator + "toolagent";
	}

	/**
	 * ツールエージェントオブジェクトを生成する。
	 * 
	 */
	public AbstractToolAgent()
	{
		// 処理なし。
	}

	/**
	 * アプリケーションを起動するツールエージェントのクラス名を取得する。
	 * 
	 * @return アプリケーションを起動するツールエージェントのクラス名
	 */
	abstract protected String getToolAgentClassName();

	/**
	 * アプリケーション実行情報を送信するキュー名を取得する。
	 * 
	 * ツールエージェント毎にキューが必要で、そのキューに名前をつけなくてはならない。 このキュー名はツールエージェント固有の文字列である。
	 * 基本的にはツールエージェント名を使用する。 各ツールエージェントで重複しないように注意すること。
	 * 
	 * @return キュー名
	 */
	abstract protected String getQueueName();

	/**
	 * キューに送信するアプリケーション実行情報を取得する。
	 * 
	 * アプリケーション実行クラスでは、この情報を基にして、 アプリケーションを実行する。
	 * 
	 * @param appName
	 *            定義に指定したアプリケーション名
	 * @param wid
	 *            作業項目ID
	 * @param handler
	 *            ツールエージェントハンドラ
	 * @param attrList
	 *            属性一覧
	 * @return アプリケーション実行情報
	 */
	abstract protected AppExecutionInfo getExecInfo(String appName, String wid,
			int handler, Attribute[] attrList);

	/**
	 * アプリケーションが定義されているかどうかチェックする。
	 * 
	 * アプリケーションが定義されていない場合、例外が発生する。
	 * 
	 * @param appName
	 *            アプリケーション名
	 * @throws ApplicationNotDefinedException
	 *             アプリケーションが定義されていない場合
	 */
	abstract protected void checkApplicationDefined(String appName)
			throws ApplicationNotDefinedException;

	/**
	 * アプリケーションの起動を行う。 <b>制限事項</b>として、本ワークフローシステム運用基盤では、 引数にモードは含まないものとする。
	 * 
	 * @param appName
	 *            定義に指定したアプリケーション名
	 * @param wid
	 *            作業項目ID
	 * @param attrList
	 *            属性一覧
	 * @return ツールエージェントハンドラ
	 * @throws ApplicationNotStartedException
	 *             アプリケーションが開始できなかった場合
	 * @throws ApplicationNotDefinedException
	 *             アプリケーションが定義されていない場合
	 * @throws ApplicationBusyException
	 *             アプリケーションがビジーで要求を受けられない場合
	 */
	public int invokeApplication(String appName, String wid,
			Attribute[] attrList) throws ApplicationNotStartedException,
			ApplicationNotDefinedException, ApplicationBusyException
	{

		// ログ
		String msg = StringManager.get("I2001") + "(ApplicationName=" + appName
				+ ", " + "WorkItemID=" + wid + ", " + "Attributes=" + attrList
				+ ")";
		log.info(msg);

		int handler = 0;

		// アプリケーションが、定義されているかどうかチェック
		checkApplicationDefined(appName);

		try {
			// ツールエージェントハンドルを取得
			handler = ToolAgentHandleCreator.generate();

			// DAOFactoryを取得
			WorkflowDAOFactory factory = DataAccessManager.getDAOFactory();

			// AppExecutionDAOを取得
			AppExecutionDAO appExecutionDao = factory.getAppExecutionDAO();

			// アプリケーション実行テーブルにアプリケーションの状態(=未実行)をINSERT
			// STARTER_ID(実行者ID)には作業項目IDを設定する
			AppExecutionBean bean = new AppExecutionBean();
			bean.setAppName(appName);
			bean.setToolAgent(getToolAgentClassName());
			bean.setExecId(handler);
			bean.setStartDate(new Timestamp(System.currentTimeMillis()));
			bean.setStarterId(wid);
			bean.setAppState(ApplicationState.NOT_STARTED);
			appExecutionDao.insert(bean);

			// キューにアプリケーション実行情報を送信
			AppExecutionInfo info = getExecInfo(appName, wid, handler, attrList);
			info.setRetryCount(this.retryCount);
			getMessageTransporter().sendMessage(info);
		} catch (Exception ex) {
			// ログ
			String errMsg = StringManager.get("E2001") + "(ApplicationName="
					+ appName + ", " + "WorkItemID=" + wid + ", "
					+ "Attributes=" + attrList + ")";
			log.error(errMsg, ex);
			throw new ApplicationNotStartedException(errMsg, ex);
		}

		// ログ
		log.info(StringManager.get("I2002"));

		return handler;
	}

	/**
	 * アプリケーションの試行回数を設定する。
	 * 
	 * @param count
	 *            アプリケーションの試行回数
	 */
	protected void setRetryCount(int count)
	{
		this.retryCount = count;
	}

	/**
	 * アプリケーションの実行状態を取得する。 実行状態は、以下のものを用意する。
	 * <ul>
	 * <li>0：正常終了</li>
	 * <li>1：異常終了</li>
	 * <li>2：未開始</li>
	 * <li>3：停止中</li>
	 * <li>6：実行中</li>
	 * <li>9：識別不可</li>
	 * </ul>
	 * <b>制限事項</b>として、本ワークフローシステム運用基盤では、 属性については、ツールエージェント、またはアプリケーションから
	 * 更新されると想定し、引数には含まないこととする。
	 * 
	 * @param toolAgentHandle
	 *            ツールエージェントハンドル
	 * @param wid
	 *            作業項目ID
	 * @return 実行状態
	 * @throws ApplicationBusyException
	 *             アプリケーションがビジーで要求を受けられない場合
	 * @throws InvalidToolAgentException
	 *             有効でないツールエージェントハンドルを指定した場合
	 * @throws InvalidWorkItemException
	 *             有効でない作業項目IDを指定した場合
	 */
	public int requestAppStatus(int toolAgentHandle, String wid)
			throws ApplicationBusyException, InvalidToolAgentException,
			InvalidWorkItemException
	{
		// ログ
		String msg = StringManager.get("I2005") + "(ToolAgentHandler="
				+ toolAgentHandle + ", " + "WorkItemID=" + wid + ")";
		log.info(msg);

		int state = ApplicationState.NOT_STARTED.toInt();

		try {
			// DAOFactoryを取得
			WorkflowDAOFactory factory = DataAccessManager.getDAOFactory();

			// AppExecutionDAOを取得
			AppExecutionDAO dao = factory.getAppExecutionDAO();

			// アプリケーション実行テーブルからアプリケーションの実行状態を取得
			List<AppExecutionBean> list = dao.findByExecID(toolAgentHandle);

			if (list.size() != 1) {
				// エラー
				String errMsg = StringManager.get("E2003")
						+ "(ToolAgentHandler=" + toolAgentHandle + ", "
						+ "WorkItemID=" + wid + ")";
				throw new InvalidToolAgentException(errMsg);
			}
			AppExecutionBean bean = list.get(0);

			state = bean.getAppState().toInt();
		} catch (InvalidToolAgentException ex) {
			log.error(StringManager.get("E2003"), ex);
			throw ex;
		} catch (Exception ex) {
			// ログ
			String errMsg = StringManager.get("E2003");
			log.error(errMsg, ex);
			throw new InvalidToolAgentException(errMsg, ex);
		}

		// ログ
		log.info(StringManager.get("I2006") + "(ExecutionState=" + state + ")");

		return state;
	}

	/**
	 * ワークフローエンジンと接続する。
	 * 
	 * ワークフローエンジンに接続していなければ、接続する。 既に接続している場合は、一旦切断して、再接続する。
	 * 
	 * @return セッションID
	 * @throws ConnectionFailedException
	 *             ワークフローエンジンとの接続に失敗した場合
	 * @see jp.co.argo21.nautica.workflow.ta.AbstractToolAgent#connect()
	 */
	protected String connect() throws ConnectionFailedException
	{
		/* ワークフローエンジンに接続いれば一旦切断する */
		if (this.session != null) {
			this.disconnect(this.session);
		}

		WorkflowEngine engine = WorkflowEngine.getInstance();
		this.session = engine.connect(this.userId, this.passwd);

		return this.session;
	}

	/**
	 * ワークフローエンジンから切断する。
	 * 
	 * @param session
	 * @see jp.co.argo21.nautica.workflow.ta.AbstractToolAgent#disconnect(java.lang.String)
	 */
	protected void disconnect(String session)
	{
		WorkflowEngine engine = WorkflowEngine.getInstance();
		try {
			engine.disconnect(this.session);
		} catch (Exception ex) {
			/* Ignore */
		}
		this.session = null;
	}

	/**
	 * メッセージ配送者を取得する。
	 * 
	 * @return メッセージ配送者
	 * @throws InvalidSessionException
	 * @throws MessagingException
	 */
	private MessageTransporter getMessageTransporter()
			throws InvalidSessionException, MessagingException
	{
		if (tp == null) {
			WorkflowEngine engine = WorkflowEngine.getInstance();
			String session = null;
			try {
				session = connect();
				tp = engine.getMessageTransporter(session, getQueueName());
			} catch (ConnectionFailedException ex) {
				String errMsg = StringManager.get("E8001");
				throw new InvalidSessionException(errMsg, ex);
			} finally {
				disconnect(session);
			}
		}
		return tp;
	}
}
