/* $Id: WorkItemContainer.java,v 1.4 2007/12/06 07:47:15 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.util.List;

import org.apache.log4j.Logger;

import jp.co.argo21.nautica.workflow.dataaccess.DAOException;
import jp.co.argo21.nautica.workflow.dataaccess.WorkItemDAO;
import jp.co.argo21.nautica.workflow.dataaccess.WorkflowDAOFactory;
import jp.co.argo21.nautica.workflow.filter.StateFilter;
import jp.co.argo21.nautica.workflow.omg.WfState;
import jp.co.argo21.nautica.workflow.security.Organization;
import jp.co.argo21.nautica.workflow.security.OrganizationManager;
import jp.co.argo21.nautica.workflow.security.Role;
import jp.co.argo21.nautica.workflow.security.RoleManager;
import jp.co.argo21.nautica.workflow.security.User;
import jp.co.argo21.nautica.workflow.security.UserManager;
import jp.co.argo21.nautica.workflow.util.StateHelper;
import jp.co.argo21.nautica.workflow.util.StringManager;
import jp.co.argo21.nautica.workflow.wfmc.Activity;
import jp.co.argo21.nautica.workflow.wfmc.ActivityState;
import jp.co.argo21.nautica.workflow.wfmc.Attribute;
import jp.co.argo21.nautica.workflow.wfmc.AttributeAssignmentFailedException;
import jp.co.argo21.nautica.workflow.wfmc.Filter;
import jp.co.argo21.nautica.workflow.wfmc.InvalidAttributeException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidFilterException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidSessionException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidSourceUserException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidStateException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidTargetUserException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidWorkItemException;
import jp.co.argo21.nautica.workflow.wfmc.Participant;
import jp.co.argo21.nautica.workflow.wfmc.TransitionNotAllowedException;
import jp.co.argo21.nautica.workflow.wfmc.WorkItem;
import jp.co.argo21.nautica.workflow.wfmc.WorkItemState;

/**
 * 作業項目プールによって作成される、セッションに対応したユーザ用の
 * 一時的作業項目コンテナ
 *
 * @author  nito(Argo 21, Corp.)
 * @version $Revision: 1.4 $
 * @since   Nautica Workflow 1.0
 */
public class WorkItemContainer
{
	/** エンジンログ */
	@SuppressWarnings("unused")
	private static Logger eLog = LogManager.getEngineLogger();
	
	/** デフォルトの有効期間（1週間) */
	private static long DEFAULT_VALID_TERM = 604800000L;

	/** コンテナの有効期間 */
	private long validTerm = DEFAULT_VALID_TERM;
	/** ワークフロー参加者 */
	private Participant userResource;
	/** 組織リソース */
	private List<Participant> orgResources;
	/** 権限リソース */
	private List<Participant> roleResources;
	/** 作成時間 */
	private long createTimeMillis;
	/** 期限切れかどうか */
	private boolean isExpired;
	
	/**
	 * セッション用のコンテナを生成する。
	 *
	 * @param user ユーザ
	 * @param orgs 組織
	 * @param roles 権限
	 */
	WorkItemContainer(Participant user, List<Participant> orgs, List<Participant> roles)
	{
		this(user, orgs, roles, DEFAULT_VALID_TERM);
	}
	
	/**
	 * セッション用のコンテナを生成する。
	 *
	 * @param user ユーザ
	 * @param orgs 組織
	 * @param roles 権限
	 * @param validTerm 有効期間 
	 */
	WorkItemContainer(Participant user, List<Participant> orgs, List<Participant> roles, long validTerm)
	{
		this.userResource = user;
		this.orgResources = orgs;
		this.roleResources = roles;
		this.createTimeMillis = System.currentTimeMillis();
		this.validTerm = validTerm;
	}
	
	/**
	 * コンテナが無効かどうかを返す。
	 *
	 * @return 無効ならtrue
	 */
	boolean isExpired()
	{
		long now = System.currentTimeMillis();
		if (now - createTimeMillis > validTerm) {
			isExpired = true;
		}
		return isExpired;
	}
	
	/**
	 * コンテナを無効にする。
	 */
	void expire()
	{
		isExpired = true;
	}

	/**
	 * 作業項目一覧の取得を行う。
	 *
	 * @param filter 検索用フィルター
	 * @return 作業項目一覧
	 * @throws InvalidFilterException 指定されたフィルターが有効でない場合
	 */
	WorkItem[] getWorkItems(Filter filter)
	throws InvalidFilterException
	{
		try {
			WorkflowDAOFactory daoFactory
				= DataAccessManager.getDAOFactory();
			WorkItemDAO workItemDAO = daoFactory.getWorkItemDAO();

			WorkItem[] items = workItemDAO.findByFilter(filter, userResource, orgResources, roleResources);
			
			return items;
		} catch (InvalidFilterException ex) {
			throw ex;
		} catch (Exception ex) {
			// 作業項目一覧の取得に失敗しました。
			String E0174 = StringManager.get("E0174");
			throw new InvalidFilterException(E0174, ex);
		}
	}

	/**
	 * 作業項目の取得を行う。
	 * このAPI呼び出しで、該当作業項目がセッションによってロックされる。
	 *
	 * @param wid 作業項目ID
	 * @return 作業項目
	 * @throws InvalidWorkItemException 作業項目IDが有効でない場合
	 */
	WorkItem getWorkItem(String wid) throws InvalidWorkItemException
	{
		try {
			//作業項目に対するアクセス権の検証
			WorkItem workItem = checkWorkItem(wid);

			return workItem;
		} catch (InvalidWorkItemException ex) {
			throw ex;
		} catch (Exception ex) {
			// 作業項目の取得に失敗しました。
			String E0116 = StringManager.get("E0116");
			throw new InvalidWorkItemException(E0116, ex);
		}
	}

	/**
	 * 作業項目の完了を行う。
	 * このAPI呼び出しで、該当作業項目がセッションによってアンロックされる。
	 *
	 * @param wid 作業項目ID
	 * @throws InvalidWorkItemException 作業項目IDが有効でない場合
	 */
	void completeWorkItem(String wid) throws InvalidWorkItemException
	{
		try {
			//作業項目に対するアクセス権の検証
			WorkItem workItem = checkWorkItem(wid);
			//実行状態かどうかを判定
			checkUsing(wid);
			
			String pid = workItem.getProcessID();
			String aid = workItem.getActivityID();

			//アクティビティの完了処理
			ProcessManagerFactory pmf = ProcessManagerFactory.getInstance();
			ProcessManager pm = pmf.getOwnerProcessManager(pid);
			ProcessInternal process = (ProcessInternal)pm.getProcess(pid);
			ActivityInternal activity = (ActivityInternal)process.getStep(aid);
			activity.changeState(ActivityState.CLOSED_COMPLETED);
			
			//不要になった作業項目を削除
			WorkflowDAOFactory daoFactory
				= DataAccessManager.getDAOFactory();
			WorkItemDAO workItemDAO = daoFactory.getWorkItemDAO();
			try {
				workItemDAO.delete(wid);
			} catch (DAOException ex) {
				//NOP
			}
		} catch (InvalidWorkItemException ex) {
			throw ex;
		} catch (Exception ex) {
			// 作業項目の完了に失敗しました。
			String E0175 = StringManager.get("E0175");
			throw new InvalidWorkItemException(E0175, ex);
		}
	}

	/**
	 * 現在の作業項目が遷移可能な状態の一覧を返す。
	 * 状態がない場合は、空の配列を返す。
	 * 利用可能なフィルターはStateFilterのみとする。
	 * このAPIは拡張された状態を扱うためのものであり、
	 * 通常はcompleteおよびsuspendが返される。
	 *
	 * @param wid 作業項目ID
	 * @param filter 検索用フィルター
	 * @return 作業項目状態一覧
	 * @throws InvalidWorkItemException 作業項目IDが有効でない場合
	 * @throws InvalidFilterException 指定されたフィルターが有効でない場合
	 */
	WorkItemState[] getWorkItemStates(String wid, Filter filter)
	throws InvalidWorkItemException, InvalidFilterException
	{
		WorkItemState state = null;
		try {
			//StateFilterのみを対象とする。
			if (filter != null) {
				if (filter instanceof StateFilter) {
					WfState st = ((StateFilter)filter).getState();
					if (st instanceof WorkItemState) {
						state = (WorkItemState)st;
					} else {
						// 作業項目状態ではありません。
						String E0183 = StringManager.get("E0183");
						throw new InvalidFilterException(E0183);
					}
				} else {
					// 状態フィルター以外は使用できません。
					String E0184 = StringManager.get("E0184");
					throw new InvalidFilterException(E0184);
				}
			}

			//作業項目に対するアクセス権の検証
			WorkItem workItem = checkWorkItem(wid);
			
			String pid = workItem.getProcessID();
			String aid = workItem.getActivityID();
			
			ProcessManagerFactory pmf = ProcessManagerFactory.getInstance();
			ProcessManager pm = pmf.getOwnerProcessManager(pid);
			ProcessInternal process = (ProcessInternal)pm.getProcess(pid);
			ActivityInternal activity = (ActivityInternal)process.getStep(aid);
			Activity act = activity.getBean();
			ActivityState astate = ActivityState.getState(act.getActivityState());
			WorkItemState wstate = StateHelper.convert(astate);

			WorkItemState[] ret = null;
			if (filter == null || state == null) {
				ret = WorkItemState.getTransitableStates(wstate.toInt());
			} else {
				//フィルタリング処理
				if (StateHelper.containsTransitable(wstate, state)) {
					ret = new WorkItemState[1];
					ret[0] = state;
				} else {
					ret = new WorkItemState[0];
				}
			}
			return ret;
		} catch (InvalidWorkItemException ex) {
			throw ex;
		} catch (InvalidFilterException ex) {
			throw ex;
		} catch (Exception ex) {
			// 作業項目状態の取得に失敗しました。
			String E0176 = StringManager.get("E0176");
			throw new InvalidWorkItemException(E0176, ex);
		}
	}
	
	/**
	 * 作業項目状態の変更を行う。
	 * ただし、本ワークフローシステム運用基盤では、クライアントから
	 * 変更可能な状態はCLOSED_COMPLETEDのみである。
	 *
	 * @param wid 作業項目ID
	 * @param state 作業項目状態
	 * @throws InvalidWorkItemException 作業項目IDが有効でない場合
	 * @throws InvalidStateException 指定された状態が有効でない場合
	 * @throws TransitionNotAllowedException 状態の変更が許可されていない場合
	 */
	void changeWorkItemState(String session, String wid, WorkItemState state)
	throws InvalidSessionException, InvalidWorkItemException, InvalidStateException,
		TransitionNotAllowedException
	{
		try {
			//作業項目に対するアクセス権の検証
			WorkItem workItem = checkWorkItem(wid);
			
			ActivityState astate = StateHelper.convert(state);
			String pid = workItem.getProcessID();
			String aid = workItem.getActivityID();

			//アクティビティの状態変更処理
			ProcessManagerFactory pmf = ProcessManagerFactory.getInstance();
			ProcessManager pm = pmf.getOwnerProcessManager(pid);
			ProcessInternal process = (ProcessInternal)pm.getProcess(pid);
			ActivityInternal activity = (ActivityInternal)process.getStep(aid);
			activity.changeState(astate);
			
			//状態が閉じた場合、不要になった作業項目を削除
			if (state.equals(WorkItemState.CLOSED_COMPLETED)
				|| state.equals(WorkItemState.CLOSED_ABORTED)
				|| state.equals(WorkItemState.CLOSED_TERMINATED)) {
				WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();
				WorkItemDAO workItemDAO = daoFactory.getWorkItemDAO();
				try {
					workItemDAO.delete(wid);
				} catch (DAOException ex) {
					//NOP
				}
			}
		} catch (InvalidWorkItemException ex) {
			throw ex;
		} catch (InvalidStateException ex) {
			throw ex;
		} catch (TransitionNotAllowedException ex) {
			throw ex;
		} catch (jp.co.argo21.nautica.workflow.omg.TransitionNotAllowedException ex) {
			throw new TransitionNotAllowedException(ex.getMessage(), ex);
		} catch (jp.co.argo21.nautica.workflow.omg.InvalidStateException ex) {
			throw new InvalidStateException(ex.getMessage(), ex);
		} catch (Exception ex) {
			// 作業項目状態の変更に失敗しました。
			String E0177 = StringManager.get("E0177");
			throw new InvalidWorkItemException(E0177, ex);
		}
	}
	
	/**
	 * 作業項目の再割り当てを行う。
	 *
	 * @param wid 作業項目ID
	 * @param sourceUser 割り当て前のユーザ
	 * @param targetUser 割り当て後のユーザ
	 * @throws InvalidWorkItemException 作業項目IDが有効でない場合
	 * @throws InvalidSourceUserException 割り当て前ユーザが有効でない場合
	 * @throws InvalidTargetUserException 割り当て先ユーザが有効でない場合
	 */
	void reassignWorkItem(String wid, Participant sourceUser, Participant targetUser)
	throws InvalidWorkItemException, InvalidSourceUserException, InvalidTargetUserException
	{
		try {
			//作業項目に対するアクセス権の検証
			@SuppressWarnings("unused")
			WorkItem workItem = checkWorkItem(wid);
			//実行状態かどうかを判定
			checkUsing(wid);
			
			//ユーザの有効性のチェック
			ResourceManagerFactory rmf = ResourceManagerFactory.getInstance();
			String tpid = targetUser.getID();
			int ptype = targetUser.getType();
			if (ptype == Participant.HUMAN || ptype == Participant.SYSTEM) {
				UserManager um = rmf.getUserManager();
				User tuser = um.getUser(tpid);
				if (tuser == null) {
					// ユーザが存在しません。
					String E0185 = StringManager.get("E0185");
					throw new InvalidTargetUserException(E0185);
				}
			} else if (ptype == Participant.ORGANIZATIONAL_UNIT) {
				OrganizationManager om = rmf.getOrganizationManager();
				Organization torg = om.getOrganization(tpid);
				if (torg == null) {
					// 組織が存在しません。
					String E0227 = StringManager.get("E0227");
					throw new InvalidTargetUserException(E0227);
				}
			} else if (ptype == Participant.ROLE) {
				RoleManager rm = rmf.getRoleManager();
				Role trole = rm.getRole(tpid);
				if (trole == null) {
					// ロールが存在しません。
					String E0228 = StringManager.get("E0228");
					throw new InvalidTargetUserException(E0228);
				}
			} else {
				String E0229 = StringManager.get("E0229");
				throw new InvalidTargetUserException(E0229);
			}
			
			WorkflowDAOFactory daoFactory
				= DataAccessManager.getDAOFactory();
			WorkItemDAO workItemDAO = daoFactory.getWorkItemDAO();

			workItemDAO.updateParticipant(wid, sourceUser, targetUser);
		} catch (InvalidWorkItemException ex) {
			throw ex;
		} catch (InvalidTargetUserException ex) {
			throw ex;
		} catch (Exception ex) {
			// 作業項目の割り当てに失敗しました。
			String E0178 = StringManager.get("E0178");
			throw new InvalidWorkItemException(E0178, ex);
		}
	}
	
	/**
	 * 作業項目属性一覧の取得を行う。
	 * 利用可能なフィルターはNameFilter, AttributeFilter, GroupFilterとする。
	 *
	 * @param wid 作業項目ID
	 * @param filter 検索用フィルター
	 * @return 作業項目属性一覧
	 * @throws InvalidWorkItemException 作業項目IDが有効でない場合
	 * @throws InvalidFilterException 指定されたフィルターが有効でない場合
	 */
	Attribute[] getWorkItemAttributes(String session, String wid, Filter filter)
	throws InvalidSessionException, InvalidWorkItemException, InvalidFilterException
	{
		try {
			//作業項目に対するアクセス権の検証
			WorkItem workItem = checkWorkItem(wid);
			//実行状態かどうかを判定
			checkUsing(wid);
			
			String pid = workItem.getProcessID();
			String aid = workItem.getActivityID();
			
			ProcessManagerFactory pmf = ProcessManagerFactory.getInstance();
			ProcessManager pm = pmf.getOwnerProcessManager(pid);
			ProcessInternal process = (ProcessInternal)pm.getProcess(pid);
			ActivityInternal activity = (ActivityInternal)process.getStep(aid);
			Attribute[] ret = (Attribute[])activity.getContext(filter);
			return ret;
		} catch (InvalidSessionException ex) {
			throw ex;
		} catch (InvalidWorkItemException ex) {
			throw ex;
		} catch (InvalidFilterException ex) {
			throw ex;
		} catch (Exception ex) {
			// 作業項目属性の取得に失敗しました。
			String E0179 = StringManager.get("E0179");
			throw new InvalidWorkItemException(E0179, ex);
		}
	}
	
	/**
	 * 作業項目属性の取得を行う。
	 *
	 * @param wid 作業項目ID
	 * @param name 属性名
	 * @return 属性
	 * @throws InvalidWorkItemException 作業項目IDが有効でない場合
	 * @throws InvalidAttributeException 属性が有効でない場合
	 */
	Attribute getWorkItemAttributeValue(String wid, String name)
	throws InvalidWorkItemException, InvalidAttributeException
	{
		try {
			//作業項目に対するアクセス権の検証
			WorkItem workItem = checkWorkItem(wid);
			//実行状態かどうかを判定
			checkUsing(wid);
			
			String pid = workItem.getProcessID();
			String aid = workItem.getActivityID();
			
			ProcessManagerFactory pmf = ProcessManagerFactory.getInstance();
			ProcessManager pm = pmf.getOwnerProcessManager(pid);
			ProcessInternal process = (ProcessInternal)pm.getProcess(pid);
			ActivityInternal activity = (ActivityInternal)process.getStep(aid);
			Attribute attr = (Attribute)activity.getContext(name);
			
			return attr;
		} catch (InvalidWorkItemException ex) {
			throw ex;
		} catch (InvalidAttributeException ex) {
			throw ex;
		} catch (Exception ex) {
			// 作業項目属性の取得に失敗しました。
			String E0179 = StringManager.get("E0179");
			throw new InvalidWorkItemException(E0179, ex);
		}
	}

	/**
	 * 作業項目属性の割り当てを行う。
	 *
	 * @param wid 作業項目ID
	 * @param attr 属性
	 * @throws InvalidWorkItemException 作業項目IDが有効でない場合
	 * @throws InvalidAttributeException 属性が有効でない場合
	 * @throws AttributeAssignmentFailedException 属性の割り当てに失敗した場合
	 */
	void assignWorkItemAttribute(String session, String wid, Attribute attr)
	throws InvalidSessionException, InvalidWorkItemException, InvalidAttributeException,
		AttributeAssignmentFailedException
	{
		try {
			//作業項目に対するアクセス権の検証
			WorkItem workItem = checkWorkItem(wid);
			//実行状態かどうかを判定
			checkUsing(wid);

			String pid = workItem.getProcessID();
			String aid = workItem.getActivityID();

			ProcessManagerFactory pmf = ProcessManagerFactory.getInstance();
			ProcessManager pm = pmf.getOwnerProcessManager(pid);
			ProcessInternal process = (ProcessInternal)pm.getProcess(pid);
			ActivityInternal activity = (ActivityInternal)process.getStep(aid);
			activity.setContext(attr);
		} catch (InvalidSessionException ex) {
			throw ex;
		} catch (InvalidWorkItemException ex) {
			throw ex;
		} catch (InvalidAttributeException ex) {
			throw ex;
		} catch (AttributeAssignmentFailedException ex) {
			throw ex;
		} catch (Exception ex) {
			// 作業項目属性の割り当てに失敗しました。
			String E0180 = StringManager.get("E0180");
			throw new InvalidWorkItemException(E0180, ex);
		}
	}

	/**
	 * 対応する作業項目のチェックを行い、それを返す。
	 *
	 * @param wid 作業項目ID
	 * @return　作業項目
	 * @throws Exception 任意の例外
	 */
	private WorkItem checkWorkItem(String wid)
	throws Exception
	{
		WorkflowDAOFactory daoFactory
			= DataAccessManager.getDAOFactory();
		WorkItemDAO workItemDAO = daoFactory.getWorkItemDAO();

		WorkItem item = workItemDAO.findByWorkItemID(wid, userResource, orgResources, roleResources);

		return item;
	}
	
	/**
	 * 作業項目が利用可能な状態かどうかを判定する。
	 *
	 * @param wid 作業項目ID
	 * @throws InvalidWorkItemException 利用可能でない場合
	 * @throws InvalidFilterException フィルターに誤りがある場合
	 */
	private void checkUsing(String wid)
	throws InvalidWorkItemException, InvalidFilterException
	{
		StateFilter filter = new StateFilter(WorkItemState.OPEN_NOT_RUNNING_SUSPENDED);
		WorkItemState[] states = getWorkItemStates(wid, filter);

		//実行状態かどうかを判定する。
		if (states.length == 0) {
			// 作業項目は実行状態ではありません。
			String E0182 = StringManager.get("E0182");
			throw new InvalidWorkItemException(E0182);
		}
	}
}
