/* $Id: DefinitionCache.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.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

import jp.co.argo21.nautica.workflow.dataaccess.DefinitionBean;
import jp.co.argo21.nautica.workflow.dataaccess.DefinitionDAO;
import jp.co.argo21.nautica.workflow.dataaccess.WorkflowDAOFactory;
import jp.co.argo21.nautica.workflow.definition.PackageDefinition;
import jp.co.argo21.nautica.workflow.definition.ProcessDefinition;
import jp.co.argo21.nautica.workflow.definition.impl.DefinitionLoader;
import jp.co.argo21.nautica.workflow.omg.WorkflowException;
import jp.co.argo21.nautica.workflow.util.StringManager;

/**
 * 定義のキャッシュシステム。
 *
 * @author  nito(Argo 21, Corp.)
 * @version $Revision: 1.2 $
 * @since   Nautica Workflow 1.0
 */
class DefinitionCache
{
	/** エンジンログ */
	private static Logger eLog = LogManager.getEngineLogger();
	
	/** 定義ローダー */
	private static DefinitionLoader loader;

	/** キャッシュする定義数の既定値。設定ファイルにて変更可能 */
	private static int DEFAULT_MAX_COUNT = 20;
	
	/** 現在の登録可能最大数 */
	private int maxCacheCount = DEFAULT_MAX_COUNT;

	
	/**
	 * 定義一覧。
	 * キャッシュされる要素数は、設定ファイルの
	 * nautica.workflow.repository.maxCacheCountの値による。
	 * デフォルトは20。
	 */
	private List<QueueItem> definitions = new ArrayList<QueueItem>();

	static
	{
		String home = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_HOME);
		String separator = SystemChecker.getProperty("file.separator");
		loader = new DefinitionLoader(home + separator + "repository");
	}
	
	/**
	 * 定義キャッシュを生成する。
	 */
	DefinitionCache()
	{
		String s = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_REPOSITORY_MAX_CACHE_COUNT);
		try {
			int count = Integer.parseInt(s);
			if (count < DEFAULT_MAX_COUNT) count = DEFAULT_MAX_COUNT;
			maxCacheCount = count;
			
			preload();
		} catch (Exception ex) {
		}
	}

	/**
	 * 最大キャッシュ可能数を返す。
	 *
	 * @return 最大キャッシュ可能数
	 */
	int getMaxCacheCount()
	{
		return maxCacheCount;
	}
	
	/**
	 * 定義名から、パッケージ定義を取得する。
	 *
	 * @param name 定義名
	 * @return パッケージ定義
	 * @throws WorkflowException 取得時に例外が起こった場合
	 */
	synchronized PackageDefinition getPackageDefinition(String name)
	throws DefinitionNotFoundException, WorkflowException
	{
		try {
			DefinitionBean bean = getDefinitionBean(name);
			return findDefinition(bean);
		} catch (WorkflowException ex) {
			throw ex;
		} catch (Exception ex) {
			// パッケージ定義の取得時にエラーが起こりました。
			String E0096 = StringManager.get("E0096");
			throw new WorkflowException(E0096, ex);
		}
	}
	
	/**
	 * 定義名からプロセス定義を返す。
	 *
	 * @param name 定義名
	 * @return プロセス定義
	 * @throws DefinitionNotFoundException 定義が見つからない場合
	 * @throws WorkflowException 任意の例外が起こった場合
	 */
	synchronized ProcessDefinition getProcessDefinition(String name)
	throws DefinitionNotFoundException, WorkflowException
	{
		try {
			DefinitionBean bean = getDefinitionBean(name);
			String id = bean.getInternalPDID();

			PackageDefinition pkg = findDefinition(bean);
			for (ProcessDefinition p : pkg.getProcesses()) {
				if (p.getID().equals(id)) {
					return p;
				}
			}
			// 対応するプロセス定義が存在しません。
			String E0094 = StringManager.get("E0094") + "(" + name + ")";
			throw new DefinitionNotFoundException(E0094);
		} catch (WorkflowException ex) {
			throw ex;
		} catch (Exception ex) {
			// プロセス定義の取得時にエラーが起こりました。
			String E0097 = StringManager.get("E0097") + "(" + name + ")";
			throw new WorkflowException(E0097, ex);
		}
	}
	
	/**
	 * 定義名から定義情報を返す。
	 *
	 * @param name 定義名
	 * @return 定義情報
	 * @throws DefinitionNotFoundException 定義が見つからない場合
	 * @throws WorkflowException 任意の例外が起こった場合
	 */
	synchronized DefinitionBean getDefinitionBean(String name)
	throws DefinitionNotFoundException, Exception
	{
		try {
			WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();
			DefinitionDAO dao = daoFactory.getDefinitionDAO();
			DefinitionBean bean = dao.findByPrimaryKey(name);

			if (bean == null) {
				// 対応する定義情報が存在しません。
				String E0095 = StringManager.get("E0095") + "(" + name + ")";
				eLog.error(E0095);
				throw new DefinitionNotFoundException(E0095);
			}
			return bean;
		} catch (DefinitionNotFoundException ex) {
			throw ex;
		} catch (WorkflowException ex) {
			throw ex;
		} catch (Exception ex) {
			// 定義情報の取得時にエラーが起こりました。
			String E0098 = StringManager.get("E0098") + "(" + name + ")";
			throw new WorkflowException(E0098, ex);
		}
	}
	
	/**
	 * 設定から定義ファイルの事前ロードを行う。
	 *
	 * @throws Exception
	 */
	private void preload() throws Exception
	{
		String s = SystemChecker.getProperty(WorkflowEngineConstants.NAUTICA_REPOSITORY_PRELOAD_PATH);
		if (s == null || s.trim().equals("")) return;

		StringTokenizer st = new StringTokenizer(s, ";");
		while (st.hasMoreTokens()) {
			String path = st.nextToken();
			try {
				preloadDefinition(path);
			} catch (Exception ex) {
				// 指定された定義ファイルパス名の事前ロードに失敗しましたが、処理を継続します。
				String W0005 = StringManager.get("W0005") + "(" + path + ")";
				eLog.warn(W0005);
			}
		}
	}
	
	/**
	 * 定義情報から、パッケージ定義を取得する。
	 *
	 * @param bean 定義情報
	 * @return パッケージ定義
	 * @throws Exception 任意の例外
	 */
	private PackageDefinition findDefinition(DefinitionBean bean)
	throws Exception
	{
		String path = bean.getPath();
		
		PackageDefinition pkg = findDefinitionByPath(path);
		if (pkg == null) {
			//定義がキャッシュされていない場合は、リポジトリからロード
			pkg = loader.load(path);
			QueueItem item = new QueueItem(path, pkg);
			cache(item);
		}
		return pkg;
	}
	
	/**
	 * パス名からパッケージ定義を事前キャッシュする。
	 *
	 * @param path パス名
	 * @throws Exception 任意の例外
	 */
	private void preloadDefinition(String path)
	throws Exception
	{		
		PackageDefinition pkg = findDefinitionByPath(path);
		if (pkg == null) {
			//定義がキャッシュされていない場合は、リポジトリからロード
			pkg = loader.load(path);
			QueueItem item = new QueueItem(path, pkg);
			cache(item);
		}
	}
	
	/**
	 * パス名からパッケージ定義を取得する。
	 *
	 * @param path パス名
	 * @return パッケージ定義
	 */
	private PackageDefinition findDefinitionByPath(String path)
	{
		PackageDefinition pkg = null;
		for (QueueItem item : definitions) {
			if (path.equals(item.path)) {
				pkg = item.packageDefinition;
			}
		}
		return pkg;
	}
	
	/**
	 * キュー要素をキャッシュする。
	 *
	 * @param item キュー要素
	 */
	private void cache(QueueItem item)
	{
		int count = definitions.size();
		if (count == getMaxCacheCount()) {
			definitions.remove(0);
		}
		definitions.add(item);
	}
	
	/**
	 * キャッシュにキューイングされる要素オブジェクト
	 *
	 * @author  nito(Argo 21, Corp.)
	 * @version $Revision: 1.2 $
	 * @since   Nautica Workflow 1.0
	 */
	private class QueueItem
	{
		/** パス名 */
		private String path;
		/** パッケージ定義 */
		private PackageDefinition packageDefinition;
		
		/**
		 * 要素を生成する。
		 *
		 * @param path パス名
		 * @param pkg パッケージ定義
		 */
		private QueueItem(String path, PackageDefinition pkg)
		{
			this.path = path;
			this.packageDefinition = pkg;
		}
	}
}
