/* $Id: SubprocessActivityBehavior.java,v 1.2 2007/11/05 09:50:54 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.engine;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import jp.co.argo21.nautica.workflow.dataaccess.ActivityBean;
import jp.co.argo21.nautica.workflow.dataaccess.ActivityDAO;
import jp.co.argo21.nautica.workflow.dataaccess.DefinitionBean;
import jp.co.argo21.nautica.workflow.dataaccess.DefinitionDAO;
import jp.co.argo21.nautica.workflow.dataaccess.ProcessBean;
import jp.co.argo21.nautica.workflow.dataaccess.ProcessWaitActivityBean;
import jp.co.argo21.nautica.workflow.dataaccess.ProcessWaitActivityDAO;
import jp.co.argo21.nautica.workflow.dataaccess.TargetProcessBean;
import jp.co.argo21.nautica.workflow.dataaccess.TargetProcessDAO;
import jp.co.argo21.nautica.workflow.dataaccess.VariableBean;
import jp.co.argo21.nautica.workflow.dataaccess.VariableDAO;
import jp.co.argo21.nautica.workflow.dataaccess.WorkflowDAOFactory;
import jp.co.argo21.nautica.workflow.definition.ActivityDefinition;
import jp.co.argo21.nautica.workflow.definition.ProcessDefinition;
import jp.co.argo21.nautica.workflow.definition.TransitionDefinition;
import jp.co.argo21.nautica.workflow.jms.WorkflowMessage;
import jp.co.argo21.nautica.workflow.omg.WfRequester;
import jp.co.argo21.nautica.workflow.util.StateHelper;
import jp.co.argo21.nautica.workflow.util.StringManager;
import jp.co.argo21.nautica.workflow.wfmc.ActivityState;
import jp.co.argo21.nautica.workflow.wfmc.Attribute;
import jp.co.argo21.nautica.workflow.wfmc.InterWorkflowConnector;
import jp.co.argo21.nautica.workflow.wfmc.Participant;
import jp.co.argo21.nautica.workflow.wfmc.ProcessState;

/**
 * サブプロセス実行アクティビティの状態ごとの動作を定義するクラスである。
 *
 * @author  nito(Argo 21, Corp.)
 * @version $Revision: 1.2 $
 * @since   Nautica Workflow 1.0
 */
public class SubprocessActivityBehavior
extends AbstractActivityBehavior
{
	/**
	 * サブプロセス実行アクティビティの状態ごとの動作を生成する。
	 */
	public SubprocessActivityBehavior()
	{
	}

	/**
	 * 固有の中止処理を行う。
	 *
	 * @param pd プロセス定義
	 * @param ad アクティビティ定義
	 * @param proc プロセス
	 * @param act アクティビティ
	 * @throws Exception 任意の例外
	 * @see jp.co.argo21.nautica.workflow.engine.ActivityBehavior#abort(jp.co.argo21.nautica.workflow.definition.ProcessDefinition, jp.co.argo21.nautica.workflow.definition.ActivityDefinition, jp.co.argo21.nautica.workflow.engine.ProcessInternal, jp.co.argo21.nautica.workflow.engine.ActivityInternal, boolean)
	 */
	public void abort(ProcessDefinition pd, ActivityDefinition ad,
			ProcessInternal proc, ActivityInternal act, boolean checkChild)
	throws Exception
	{
		String scope = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_SCOPE);
		if (scope == null) scope = "";
		scope = scope.trim();
		String engineName = ad.getEngineName();
		if (engineName == null
				|| engineName.trim().equals("")
				|| engineName.trim().equals(scope)) {
			terminateOrAbortOnLocalEngine(pd, ad, proc, act, ProcessState.CLOSED_ABORTED);
		} else {
			terminateOrAbortOnRemoteEngine(pd, ad, proc, act, ProcessState.CLOSED_ABORTED);
		}
		
		WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();

		//該当アクティビティを中断させる。
		ActivityDAO actDAO = daoFactory.getActivityDAO();
		Timestamp ts = DataAccessManager.getAccessTime();
		actDAO.updateState(act.getBean(), ActivityState.CLOSED_ABORTED, ts);
	}

	/**
	 * 固有の開始処理を行う。
	 *
	 * @param pd プロセス定義
	 * @param ad アクティビティ定義
	 * @param proc プロセス
	 * @param act アクティビティ
	 * @throws Exception 任意の例外
	 * @see jp.co.argo21.nautica.workflow.engine.ActivityBehavior#start(jp.co.argo21.nautica.workflow.definition.ProcessDefinition, jp.co.argo21.nautica.workflow.definition.ActivityDefinition, jp.co.argo21.nautica.workflow.engine.ProcessInternal, jp.co.argo21.nautica.workflow.engine.ActivityInternal)
	 */
	public void start(ProcessDefinition pd, ActivityDefinition ad,
			ProcessInternal proc, ActivityInternal act)
	throws Exception
	{
		try {
			String scope = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_SCOPE);
			if (scope == null) scope = "";
			scope = scope.trim();
			String engineName = ad.getEngineName();
			if (engineName == null
					|| engineName.trim().equals("")
					|| engineName.trim().equals(scope)) {
				startOnLocalEngine(pd, ad, proc, act);
			} else {
				startOnRemoteEngine(pd, ad, proc, act);
			}
		} catch (Exception ex) {
			// サブプロセスアクティビティの開始に失敗しました。
			String E0160 = StringManager.get("E0160");
			eLog.fatal(E0160, ex);
			act.terminate();
		}
		
	}

	/**
	 * 固有の停止処理を行う。
	 *
	 * @param pd プロセス定義
	 * @param ad アクティビティ定義
	 * @param proc プロセス
	 * @param act アクティビティ
	 * @throws Exception 任意の例外
	 * @see jp.co.argo21.nautica.workflow.engine.ActivityBehavior#terminate(jp.co.argo21.nautica.workflow.definition.ProcessDefinition, jp.co.argo21.nautica.workflow.definition.ActivityDefinition, jp.co.argo21.nautica.workflow.engine.ProcessInternal, jp.co.argo21.nautica.workflow.engine.ActivityInternal, boolean)
	 */
	public void terminate(ProcessDefinition pd, ActivityDefinition ad,
			ProcessInternal proc, ActivityInternal act, boolean checkChild)
	throws Exception
	{
		ProcessBean pbean = proc.getBean();
		String pid = pbean.getID();
		ActivityBean abean = act.getBean();
		String bid = abean.getBlockID();

		WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();

		if (checkChild) {
			String scope = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_SCOPE);
			if (scope == null) scope = "";
			scope = scope.trim();
			String engineName = ad.getEngineName();
			if (engineName == null
					|| engineName.trim().equals("")
					|| engineName.trim().equals(scope)) {
				terminateOrAbortOnLocalEngine(pd, ad, proc, act, ProcessState.CLOSED_TERMINATED);
			} else {
				terminateOrAbortOnRemoteEngine(pd, ad, proc, act, ProcessState.CLOSED_TERMINATED);
			}
		}
		
		VariableDAO varDAO = daoFactory.getVariableDAO();
		List<VariableBean> vars = varDAO.findByProcessIDAsList(pid);
		List<TransitionDefinition> trs = ConditionEvaluator.evaluate(pd, ad, vars, true);
		
		List<ActivityBean> acts = new ArrayList<ActivityBean>();
		for (TransitionDefinition tr : trs) {
			ActivityDefinition dest = tr.getDestination();
			
			//遷移先アクティビティの生成
			ActivityBean ab = createActivity(pd, dest, pbean, bid);
			acts.add(ab);
		}
		
		if (acts.size() == 0) {
			//遷移先が見つからない。定義ミスの可能性あり。
			suspend(pd, ad, proc, act);
			String msg = StringManager.get("F0001");
			eLog.fatal(msg);
			return;
		}
		
		//該当アクティビティを中止させる。
		ActivityDAO actDAO = daoFactory.getActivityDAO();
		Timestamp ts = DataAccessManager.getAccessTime();
		actDAO.updateState(act.getBean(), ActivityState.CLOSED_TERMINATED, ts);

		//遷移先アクティビティをメッセージキューに登録する。
		for (ActivityBean ab : acts) {
			String abid = ab.getActivityID();
			WorkflowMessage sarmsg = new StartActivityRequestMessage(pid, abid);
			SystemMessageTransporter mt = new SystemMessageTransporter(StartActivityRequestWatcher.NAME);
			mt.sendMessage(sarmsg);
		}
	}

	/**
	 * ローカルエンジン上のプロセスの開始を行う。
	 *
	 * @param pd プロセス定義
	 * @param ad アクティビティ定義
	 * @param proc プロセス
	 * @param act アクティビティ
	 * @throws Exception 任意の例外
	 */
	private void startOnLocalEngine(ProcessDefinition pd, ActivityDefinition ad,
			ProcessInternal proc, ActivityInternal act)
	throws Exception
	{
		ProcessBean pbean = proc.getBean();
		ActivityBean abean = act.getBean();
		String pid = abean.getProcessID();
		String aid = abean.getActivityID();
		String spdid = ad.getSubprocessID();
		List<String> sparams = ad.getSubprocessParameters();
		int exec = ad.getExecution();

		//該当アクティビティを開始させる。
		WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();
		ActivityDAO actDAO = daoFactory.getActivityDAO();
		Timestamp ts = DataAccessManager.getAccessTime();
		actDAO.updateState(act.getBean(), ActivityState.OPEN_RUNNING, ts);

		//サブプロセス定義の正式名を取得する。
		String pdid = pbean.getProcDefinitionID();
		DefinitionDAO defDAO = daoFactory.getDefinitionDAO();
		DefinitionBean defbean = defDAO.findByPrimaryKey(pdid);
		String path = defbean.getPath();
		DefinitionBean subdefbean = defDAO.findByPathAndID(path, spdid);
		String subdefname = subdefbean.getProcessDefinitionID();

		ProcessInternal sprocess = null;
		//サブプロセスを生成する。
		Participant system = ParticipantImpl.getSystemParticipant();
		RequesterManager rqm = RequesterManager.getInstance();
		WfRequester requester = rqm.getRequesterByUserID(system.getID());
		ProcessManagerFactory pmf = ProcessManagerFactory.getInstance();
		ProcessManager pm = pmf.getProcessManager(subdefname);
		sprocess = (ProcessInternal)pm.createProcess(requester, system.getName());
		ProcessBean spb = sprocess.getBean();
		String spid =spb.getID();

		//パラメータ情報の作成
		Iterator<String> spit = sparams.iterator();
		for (int i = 0; spit.hasNext(); i++) {
			String varID = (String)spit.next();
			VariableDAO varDAO = daoFactory.getVariableDAO();
			VariableBean svbean = varDAO.findByDefinitionID(pid, varID);
			VariableBean dvbean = varDAO.findByDefinitionID(spid, varID);
			String val = svbean.getValue();
			varDAO.update(dvbean, val, aid, DataAccessManager.getAccessTime());
		}

		if (exec == ActivityDefinition.SYNCHR) {
			//同期の場合、同期待ちテーブルに登録を行う
			ProcessWaitActivityDAO pwDAO = daoFactory.getProcessWaitActivityDAO();
			
			ProcessWaitActivityBean pwbean = new ProcessWaitActivityBean();
			pwbean.setProcID(pid);
			pwbean.setActID(aid);
			pwbean.setSubprocID(spid);
			pwDAO.insert(pwbean);
		}

		//プロセス開始要求
		WorkflowMessage msg = new StartProcessRequestMessage(spb.getID());
		SystemMessageTransporter mt = new SystemMessageTransporter(StartProcessRequestWatcher.NAME);
		mt.sendMessage(msg);

		if (exec == ActivityDefinition.ASYNCHR) {
			//非同期の時、結果を待たないのでアクティビティを完了させる
			//同期の場合は何もせず状態を実行中のままにする
			act.complete();
		}
	}

	/**
	 * リモートエンジン上のプロセスの開始を行う。
	 *
	 * @param pd プロセス定義
	 * @param ad アクティビティ定義
	 * @param proc プロセス
	 * @param act アクティビティ
	 * @throws Exception 任意の例外
	 */
	private void startOnRemoteEngine(ProcessDefinition pd, ActivityDefinition ad,
			ProcessInternal proc, ActivityInternal act)
	throws Exception
	{
		String engineName = ad.getEngineName();
		ProcessBean pbean = proc.getBean();
		ActivityBean abean = act.getBean();
		String pid = abean.getProcessID();
		String aid = abean.getActivityID();
		String spdid = ad.getSubprocessID();
		List<String> sparams = ad.getSubprocessParameters();
		int exec = ad.getExecution();

		//該当アクティビティを開始させる。
		WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();
		ActivityDAO actDAO = daoFactory.getActivityDAO();
		Timestamp ts = DataAccessManager.getAccessTime();
		actDAO.updateState(act.getBean(), ActivityState.OPEN_RUNNING, ts);

		//サブプロセス定義の正式名を取得する。
		String pdid = pbean.getProcDefinitionID();
		DefinitionDAO defDAO = daoFactory.getDefinitionDAO();
		DefinitionBean defbean = defDAO.findByPrimaryKey(pdid);
		String path = defbean.getPath();
		DefinitionBean subdefbean = defDAO.findByPathAndID(path, spdid);
		String subdefname = subdefbean.getProcessDefinitionID();

		//リモートのエンジンにプロセス生成要求を行う。
		InterWorkflowConnector iwc = InterWorkflowConnectorImpl.getRemoteInstance(engineName);
		String eid = InterWorkflowConnectorImpl.connectToRemoteEngine(iwc);

		int flag = InterWorkflowConnector.NOT_NEED_RESULT;
		if (exec == ActivityDefinition.SYNCHR) {
			flag = InterWorkflowConnector.NEED_RESULT;
		}
		String userid = ParticipantImpl.SYSTEM_ID;
		String roleid = SystemRoleImpl.REMOTE_ENGINE_ROLE_ID;
		String spid = iwc.createProcess(eid, subdefname, flag, pid, aid, userid, roleid);

		//パラメータ情報の作成と設定
		Attribute[] attrs = new Attribute[sparams.size()];
		Iterator<String> spit = sparams.iterator();
		for (int i = 0; spit.hasNext(); i++) {
			String varID = (String)spit.next();
			VariableDAO varDAO = daoFactory.getVariableDAO();
			VariableBean vbean = varDAO.findByDefinitionID(pid, varID);
			attrs[i] = vbean;
		}
		iwc.setProcessAttributes(eid, pid, aid, spid, attrs);

		if (flag == InterWorkflowConnector.NEED_RESULT) {
			//同期の場合、同期待ちテーブルに登録を行う
			TargetProcessDAO tpDAO = daoFactory.getTargetProcessDAO();
			
			TargetProcessBean tpbean = new TargetProcessBean();
			tpbean.setEngineID(engineName);
			tpbean.setRootProcID(pid);
			tpbean.setRootActID(aid);
			tpbean.setProcessID(spid);
			tpDAO.insert(tpbean);
		}

		iwc.disconnect(eid);

		//プロセス開始要求
		WorkflowMessage msg = new StartProcessRequestMessage(engineName, spid);
		SystemMessageTransporter mt = new SystemMessageTransporter(StartProcessRequestWatcher.NAME);
		mt.sendMessage(msg);
		
		if (flag == InterWorkflowConnector.NOT_NEED_RESULT) {
			//非同期の時、結果を待たないのでアクティビティを完了させる
			//同期の場合は何もせず状態を実行中のままにする
			act.complete();
		}
	}

	/**
	 * ローカルエンジン上のプロセスの停止または中止を行う。
	 *
	 * @param pd プロセス定義
	 * @param ad アクティビティ定義
	 * @param proc プロセス
	 * @param act アクティビティ
	 * @param state プロセス状態
	 * @throws Exception 任意の例外
	 */
	private void terminateOrAbortOnLocalEngine(ProcessDefinition pd, ActivityDefinition ad,
			ProcessInternal proc, ActivityInternal act, ProcessState state)
	throws Exception
	{
		ActivityBean abean = act.getBean();
		String pid = abean.getProcessID();
		String aid = abean.getActivityID();

		WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();
		ProcessWaitActivityDAO pwDAO = daoFactory.getProcessWaitActivityDAO();
		ProcessWaitActivityBean pwabean = pwDAO.findByProcIDANDActID(pid, aid);
		
		if (pwabean != null) {
			//メッセージキューにプロセス完了要求登録
			String spid = pwabean.getSubprocID();
			WorkflowMessage eprmsg = new EndProcessRequestMessage(spid, ActivityState.CLOSED_ABORTED);
			SystemMessageTransporter mt = new SystemMessageTransporter(EndProcessRequestWatcher.NAME);
			mt.sendMessage(eprmsg);
		}
	}

	/**
	 * リモートエンジン上のプロセスの停止または中止を行う。
	 *
	 * @param pd プロセス定義
	 * @param ad アクティビティ定義
	 * @param proc プロセス
	 * @param act アクティビティ
	 * @param state プロセス状態
	 * @throws Exception 任意の例外
	 */
	private void terminateOrAbortOnRemoteEngine(ProcessDefinition pd, ActivityDefinition ad,
			ProcessInternal proc, ActivityInternal act, ProcessState state)
	throws Exception
	{
		String engineName = ad.getEngineName();
		ActivityBean abean = act.getBean();
		String pid = abean.getProcessID();
		String aid = abean.getActivityID();

		WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();
		TargetProcessDAO tpDAO = daoFactory.getTargetProcessDAO();
		TargetProcessBean tpbean = tpDAO.findByRootID(pid, aid);
		
		if (tpbean != null) {
			//メッセージキューにプロセス完了要求登録
			String spid = tpbean.getProcessID();
			ActivityState astate = StateHelper.convert(state);
			WorkflowMessage eprmsg = new EndProcessRequestMessage(engineName, spid, astate);
			SystemMessageTransporter mt = new SystemMessageTransporter(EndProcessRequestWatcher.NAME);
			mt.sendMessage(eprmsg);

		}
	}
}
