/* $Id: WorkflowEngine.java,v 1.3 2007/11/26 08:51:40 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.io.File;
import java.io.IOException;

import org.apache.log4j.Logger;

import jp.co.argo21.nautica.workflow.jms.MessagingException;
import jp.co.argo21.nautica.workflow.jms.WorkflowMQ;
import jp.co.argo21.nautica.workflow.jms.WorkflowMessageAdapter;
import jp.co.argo21.nautica.workflow.omg.WorkflowException;
import jp.co.argo21.nautica.workflow.security.SessionManager;
import jp.co.argo21.nautica.workflow.util.StringManager;
import jp.co.argo21.nautica.workflow.wfmc.ConnectionFailedException;
import jp.co.argo21.nautica.workflow.wfmc.DefinitionRepository;
import jp.co.argo21.nautica.workflow.wfmc.InterWorkflowConnector;
import jp.co.argo21.nautica.workflow.wfmc.InvalidSessionException;
import jp.co.argo21.nautica.workflow.wfmc.WorkItemHandler;
import jp.co.argo21.nautica.workflow.wfmc.WorkflowAdminHandler;
import jp.co.argo21.nautica.workflow.wfmc.WorkflowEngineHandler;
import jp.co.argo21.nautica.workflow.wfmc.WorkflowServiceManager;

/**
 * ワークフローエンジンクラスである。
 *
 * @author  nito(Argo 21, Corp.)
 * @version $Revision: 1.3 $
 * @since   Nautica Workflow 1.0
 */
public final class WorkflowEngine
implements WorkflowServiceManager, WorkflowEngineService, ResourceManagerFactorySource
{
	private static final WorkflowEngine singleton = new WorkflowEngine();

	/** ロックファイル名 */
	private static final String LOCKFILE = "nautica.lck";

	/** エンジンログ */
	private static Logger eLog;
		
	/** 定義リポジトリ */
	private DefinitionRepositoryImpl definitionRepository;

	/** ワークフローエンジンハンドラ */
	private WorkflowEngineHandlerImpl workflowEngineHandler;

	/** ワークフロー管理ハンドラ */
	private WorkflowAdminHandlerImpl workflowAdminHandler;

	/** 作業項目ハンドラ */
	private WorkItemHandlerImpl workItemHandler;

	public static WorkflowEngine getInstance()
	{
		return singleton;
	}
	
	private WorkflowEngine()
	{
	}

	public void start(String adminKey, String homeDir) throws Exception
	{
		System.setProperty(WorkflowEngineConstants.NAUTICA_HOME, homeDir);
		
		SystemChecker.checkBootPath();
		SystemChecker.checkAdmin(adminKey);
		
		//既に起動済みかどうかを確認する。
		if (! createLockfile()) {
			throw new WorkflowException(StringManager.get("E0001"));
		}

		try {
			init();
			eLog.info(StringManager.get("I0002"));
		} catch (Exception ex) {
			forceDeleteLockfile();
			String E0002 = StringManager.get("E0002");
			eLog.error(E0002, ex);
			throw ex;
		}
	}

	public void stop(String adminKey) throws Exception
	{
		SystemChecker.checkAdmin(adminKey);
		try {
			shutdown();
			eLog.info(StringManager.get("I0003"));
		} catch (Exception ex) {
			String E0002 = StringManager.get("E0003");
			eLog.error(E0002, ex);
		} finally {
			forceDeleteLockfile();
		}
	}

	/**
	 * ロックファイルを生成する。
	 *
	 * @return ロックファイルの生成に成功した場合はtrue
	 */
	private boolean createLockfile()
	{
		String home = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_HOME);
		String separator = SystemChecker.getProperty("file.separator");
		String path = home + separator + "temp" + separator + LOCKFILE;

		try {
			File lockFile = new File(path);
			boolean isSuccess = lockFile.createNewFile();
			return isSuccess;
		} catch (IOException ex) {
			return false;
		}
	}

	/**
	 * ロックファイルが存在する場合、無条件に削除する。
	 */
	private void forceDeleteLockfile()
	{
		String home = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_HOME);
		String separator = SystemChecker.getProperty("file.separator");
		String path = home + separator + "temp" + separator + LOCKFILE;

		File lockFile = new File(path);
		if (lockFile.exists()) {
			lockFile.delete();
		}
	}

	/**
	 * ワークフローエンジンへの接続認証を行い、
	 * セッションIDを取得する。
	 *
	 * @param user ユーザID
	 * @param pass パスワード
	 * @return セッションID
	 * @throws ConnectionFailedException 接続認証に失敗した場合
	 */
	public String connect(String user, String pass)
	throws ConnectionFailedException
	{
		String argInfo = "(WfMC API = connect,"
			 + "user = [" + user + "],"
			 + "password = [**SECURITY**])";
	
		try {
			if (user == null || user.trim().equals("")) {
				// ユーザIDが未設定です。
				String E0166 = StringManager.get("E0166");
				throw new ConnectionFailedException(E0166);
			}

			if (pass == null) {
				// パスワードが未設定です。
				String E0167 = StringManager.get("E0167");
				throw new ConnectionFailedException(E0167);
			}
		
			DataAccessManager.begin(false);
				
			SessionManagerFactory factory = SessionManagerFactory.getInstance();
			SessionManager manager = factory.getSessionManager();
			String session = manager.createSession(user, pass);
	
			DataAccessManager.commit();
	
			return session;
		} catch (ConnectionFailedException ex) {
			try { DataAccessManager.rollback(); } catch (Exception ex2) { /* Ignore */ }
			String msg = ex.getMessage() + argInfo;
			eLog.error(msg, ex);
			throw ex;
		} catch (Exception ex) {
			try { DataAccessManager.rollback(); } catch (Exception ex2) { /* Ignore */ }
			// ワークフローエンジンへの認証に失敗しました。
			String E0172 = StringManager.get("E0172") + argInfo;
			eLog.error(E0172, ex);
			throw new ConnectionFailedException(E0172, ex);
		} finally {
			try { DataAccessManager.close(); } catch (Exception ex) { /* Ignore */ }
		}		
	}

	/**		GBeanInfoBuilder builder =
			new GBeanInfoBuilder("WorkflowServiceManagerGBean", WorkflowServiceManagerGBean.class);

	 * ワークフローエンジンとの切断を行う。
	 *
	 * @param session セッションID
	 * @throws InvalidSessionException 指定されたセッションが無効の場合
	 */
	public void disconnect(String session)
	throws InvalidSessionException
	{
		String argInfo = "(WfMC API = disconnect,"
			 + "session = [" + session + "])";
	
		try {
			DataAccessManager.begin(false);
			
			SessionManagerFactory factory = SessionManagerFactory.getInstance();
			SessionManager manager = factory.getSessionManager();
			manager.invalidateSession(session);
			
			DataAccessManager.commit();

		} catch (InvalidSessionException ex) {
			try { DataAccessManager.rollback(); } catch (Exception ex2) { /* Ignore */ }
			String msg = ex.getMessage() + argInfo;
			eLog.error(msg, ex);
			throw ex;
		} catch (Exception ex) {
			try { DataAccessManager.rollback(); } catch (Exception ex2) { /* Ignore */ }
			// ワークフローエンジンからの切断に失敗しました。
			String E0173 = StringManager.get("E0173") + argInfo;
			eLog.error(E0173, ex);
			throw new InvalidSessionException(E0173, ex);
		} finally {
			try { DataAccessManager.close(); } catch (Exception ex) { /* Ignore */ }
		}
	}

	/**
	 * 定義リポジトリを返す。
	 *
	 * @param session セッションID
	 * @return 定義リポジトリ
	 * @throws InvalidSessionException 指定されたセッションが無効の場合
	 */
	public DefinitionRepository getDefinitionRepository(String session)
	throws InvalidSessionException
	{
		validateSession(session);

		return definitionRepository;
	}

	/**
	 * ワークフローエンジンハンドラを返す。
	 *
	 * @param session セッションID
	 * @return ワークフローエンジンハンドラ
	 * @throws InvalidSessionException 指定されたセッションが無効の場合
	 */
	public WorkflowEngineHandler getWorkflowEngineHandler(String session)
	throws InvalidSessionException
	{
		validateSession(session);

		return workflowEngineHandler;
	}

	/**
	 * ワークフロー管理ハンドラを返す。
	 *
	 * @param session セッションID
	 * @return ワークフロー管理ハンドラ
	 * @throws InvalidSessionException 指定されたセッションが無効の場合
	 */
	public WorkflowAdminHandler getWorkflowAdminHandler(String session)
	throws InvalidSessionException
	{
		validateSession(session);

		return workflowAdminHandler;
	}

	/**
	 * 作業項目ハンドラを返す。
	 *
	 * @param session セッションID
	 * @return 作業項目ハンドラ
	 * @throws InvalidSessionException 指定されたセッションが無効の場合
	 */
	public WorkItemHandler getWorkItemHandler(String session)
	throws InvalidSessionException
	{
		validateSession(session);

		return workItemHandler;
	}

	/**
	 * 指定された名前で、メッセージキューを作成する。
	 *
	 * @param session セッションID
	 * @param queueName キュー名
	 * @param adapter メッセージ受信用のアダプタ
	 * @throws InvalidSessionException 指定されたセッションが無効の場合
	 * @throws WorkflowException キューが既にある場合などは例外となる。
	 */
	public void generateQueue(String session, String queueName,
			WorkflowMessageAdapter adapter)
	throws InvalidSessionException, WorkflowException
	{
		validateSession(session);
		
		if (queueName == null || queueName.trim().equals("")) {
			// メッセージキュー名が未設定です。
			String E0168 = StringManager.get("E0168");
			throw new WorkflowException(E0168);
		}

		if (adapter == null) {
			// メッセージ受信アダプタが未設定です。
			String E0169 = StringManager.get("E0169");
			throw new WorkflowException(E0169);
		}

		MessageBroker broker = MessageBroker.getInstance();
		broker.generateQueue(queueName, adapter);
	}

	/**
	 * メッセージ転送を返す。
	 *
	 * @param session セッションID
	 * @param queueName キュー名
	 * @return メッセージ転送
	 * @throws InvalidSessionException 指定されたセッションが無効の場合
	 * @throws MessagingException キューが存在しない場合
	 */
	public MessageTransporter getMessageTransporter(String session, String queueName)
	throws InvalidSessionException, MessagingException
	{
		validateSession(session);
		
		if (queueName == null || queueName.trim().equals("")) {
			// メッセージキュー名が未設定です。
			String E0168 = StringManager.get("E0168");
			throw new MessagingException(E0168);
		}

		//キューの存在チェック
		MessageBroker broker = MessageBroker.getInstance();
		WorkflowMQ mq = broker.getWorkflowMQ(queueName);
		if (mq == null) {
			// メッセージキューが存在しません。
			String E0171 = StringManager.get("E0171") + "(Queue = " + queueName + ")";
			throw new MessagingException(E0171);
		}

		return new MessageTransporter(queueName);
	}

	/**
	 * 
	 *
	 * @return
	 * @see jp.co.argo21.nautica.workflow.engine.WorkflowEngineService#getInterWorkflowConnector()
	 */
	@Override
	public InterWorkflowConnector getInterWorkflowConnector()
	{
		return InterWorkflowConnectorImpl.getInstance();
	}
	
	/**
	 * 
	 *
	 * @return
	 */
	public ResourceManagerFactory getResourceManagerFactory()
	{
		return ResourceManagerFactory.getInstance();
	}

	/**
	 * 定義リポジトリを返す。
	 * スーパーバイザー用。
	 *
	 * @return 定義リポジトリ
	 */
	DefinitionRepositoryImpl getDefinitionRepository()
	{
		return definitionRepository;
	}

	/**
	 * ワークフローエンジンハンドラを返す。
	 * スーパーバイザー用。
	 *
	 * @return ワークフローエンジンハンドラ
	 */
	WorkflowEngineHandlerImpl getWorkflowEngineHandler()
	{
		return workflowEngineHandler;
	}

	/**
	 * ワークフロー管理ハンドラを返す。
	 * スーパーバイザー用。
	 *
	 * @return ワークフロー管理ハンドラ
	 */
	WorkflowAdminHandlerImpl getWorkflowAdminHandler()
	{
		return workflowAdminHandler;
	}

	/**
	 * 作業項目ハンドラを返す。
	 * スーパーバイザー用。
	 *
	 * @return 作業項目ハンドラ
	 */
	WorkItemHandlerImpl getWorkItemHandler()
	{
		return workItemHandler;
	}

	/**
	 * メッセージキューにメッセージを送る。
	 * スーパーバイザー用。
	 *
	 * @param queueName キュー名
	 * @throws MessagingException キューが存在しない場合
	 */
	MessageTransporter getMessageTransporter(String queueName)
	throws MessagingException
	{
		//キューの存在チェック
		MessageBroker broker = MessageBroker.getInstance();
		WorkflowMQ mq = broker.getWorkflowMQ(queueName);
		if (mq == null) {
			// メッセージキューが存在しません。
			String E0171 = StringManager.get("E0171") + "(Queue = " + queueName + ")";
			throw new MessagingException(E0171);
		}

		return new MessageTransporter(queueName);
	}
	
	/**
	 * WorkflowEngineを初期化する。
	 *
	 * @throws Exception 任意の例外
	 */
	private void init() throws Exception
	{
		LogManager.create();
		eLog = LogManager.getEngineLogger();
		eLog.info(StringManager.get("I0001"));

		MessageBroker.create();
		eLog.info(StringManager.get("I0004"));
		
		ResourceManagerFactory.create();
		eLog.info(StringManager.get("I0005"));

		SessionManagerFactory.create();
		eLog.info(StringManager.get("I0006"));
		
		DataAccessManager.create();
		eLog.info(StringManager.get("I0025"));
		
		RequesterManager.create();
		eLog.info(StringManager.get("I0026"));
		
		ToolAgentManager.create();
		eLog.info(StringManager.get("I0027"));
		
		ProcessManagerFactory.create();
		eLog.info(StringManager.get("I0028"));

		ActivityBehaviorFactory.create();
		eLog.info(StringManager.get("I0029"));

		WorkItemPool.create();
		eLog.info(StringManager.get("I0030"));
				
		//エンジン間接続は必要なら生成
		String interWorkflow = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_INTER_WORKFLOW);
		if (interWorkflow != null && interWorkflow.equals("enable")) {
			InterWorkflowConnectorImpl.create();
			eLog.info(StringManager.get("I0007"));
		}
		
		bindLocalService();
		eLog.info(StringManager.get("I0008"));

		MessageBroker broker = MessageBroker.getInstance();
		broker.generateQueue(StartProcessRequestWatcher.NAME,
				new StartProcessRequestWatcher());
		eLog.info(StringManager.get("I0041"));
		broker.generateQueue(StartActivityRequestWatcher.NAME,
				new StartActivityRequestWatcher());
		eLog.info(StringManager.get("I0042"));
		broker.generateQueue(EndActivityRequestWatcher.NAME,
				new EndActivityRequestWatcher());
		eLog.info(StringManager.get("I0043"));
		broker.generateQueue(EndProcessRequestWatcher.NAME,
				new EndProcessRequestWatcher());
		eLog.info(StringManager.get("I0044"));
	}

	/**
	 * ローカルサービスをバインドする。
	 *
	 * @throws Exception 任意の例外
	 */
	private void bindLocalService() throws Exception
	{
		definitionRepository = new DefinitionRepositoryImpl();
		eLog.info(StringManager.get("I0010"));
		workflowEngineHandler = new WorkflowEngineHandlerImpl();
		eLog.info(StringManager.get("I0011"));
		workflowAdminHandler = new WorkflowAdminHandlerImpl();
		eLog.info(StringManager.get("I0012"));
		workItemHandler = new WorkItemHandlerImpl();
		eLog.info(StringManager.get("I0013"));
	}
	
	/**
	 * シャットダウン処理を行う。
	 */
	private void shutdown() throws Exception
	{
		MessageBroker broker = MessageBroker.getInstance();
		broker.shutdown();

		//サービスの停止
		unbindLocalService();
		eLog.info(StringManager.get("I0017"));
	}

	/**
	 * ローカルサービスをアンバインドする。
	 *
	 * @throws Exception 任意の例外
	 */
	private void unbindLocalService() throws Exception
	{
	    definitionRepository.unbind();
	    definitionRepository = null;
		eLog.info(StringManager.get("I0018"));
		workflowEngineHandler = null;
		eLog.info(StringManager.get("I0019"));
		workflowAdminHandler = null;
		eLog.info(StringManager.get("I0020"));
		workItemHandler = null;
		eLog.info(StringManager.get("I0021"));
	}
	
	/**
	 * セッションを検証する。
	 *
	 * @param session セッションID
	 * @throws InvalidSessionException 指定されたセッションが無効の場合
	 */
	private void validateSession(String session)
	throws InvalidSessionException
	{
		if (session == null || session.trim().equals("")) {
			// セッションIDが未設定です。
			String E0170 = StringManager.get("E0170");
			throw new InvalidSessionException(E0170);
		}

		SessionManagerFactory factory = SessionManagerFactory.getInstance();
		SessionManager manager = factory.getSessionManager();
		manager.validateSession(session);
	}
}
