/* $Id: WorkflowServiceAccessorManager.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.client;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import jp.co.argo21.nautica.workflow.util.StringManager;
import jp.co.argo21.nautica.workflow.wfmc.ConnectionFailedException;
import jp.co.argo21.nautica.workflow.wfmc.InvalidSessionException;

import org.apache.log4j.Logger;


/**
 * ワークフローサービスアクセッサを、セッション単位で管理するクラスである。
 * ワークフローエンジンに対するクライアントライブラリである。
 *
 * @author  ysahara(Argo 21, Corp.)
 * @version $Revision: 1.2 $
 * @since   Nautica Workflow 1.0
 */
public class WorkflowServiceAccessorManager {

	/** ログ出力オブジェクト */
	private static Logger log = 
		Logger.getLogger(WorkflowServiceAccessorManager.class);
	
	/** このクラスのインスタンス */
	private static WorkflowServiceAccessorManager wsam = 
		new WorkflowServiceAccessorManager();
	
	/** 
	 * タイムアウトをチェックする間隔（ミリ秒）。
	 */
	private static final long CHECK_TIMEOUT_INTERVAL = 1800000L;
	
	/**
	 * セッション有効期間（ミリ秒）
	 */
	private static final long VALID_TERM = 1800000L;
	
	/** 
	 * ワークフローサービスアクセッサ
	 * キー：セッションID
	 * 値：ManagedServiceAccessor オブジェクト
	 */ 
	private Map<String, ManagedServiceAccessor> accessors = 
		Collections.synchronizedMap(new HashMap<String, ManagedServiceAccessor>());
	
	/**
	 * ワークフローサービスアクセッサ管理を生成する。
	 * 
	 */
	private WorkflowServiceAccessorManager() {
		TimerTask task = new TimeoutTimerTask(this);
		new Timer().schedule(task, 0, CHECK_TIMEOUT_INTERVAL);
	}
	
	/**
	 * ワークフローサービスアクセッサ管理を返す。
	 *
	 */
	public static WorkflowServiceAccessorManager getInstance() {
		return wsam;
	}
	
	/**
	 * 接続情報を使用して、ワークフローサービスアクセッサを生成し、
	 * それをセッションID単位で保持する。
	 * ワークフローサービスアクセッサ内部では、接続情報を使用して、
	 * ワークフローサービスマネージャし、それを保持する。
	 * その後、ワークフローサービスマネージャに対して、認証を行う。
	 * 認証に成功した場合は、サービスマネージャの参照を保持する。
	 * close()が呼ばれていない場合は、まずclose()を行ってから、
	 * 接続を開きなおす。その場合、以前のセッションIDは無効となる。
	 * 
	 * 
	 * @param conn 接続情報
	 * @return セッションID
	 * @throws ConnectionFailedException 認証失敗の場合
	 */
	public String open(ConnectionInfo conn) 
	throws ConnectionFailedException
	{
		WorkflowServiceAccessor wsa = 
			new WorkflowServiceAccessor(conn);
		String sessionID = wsa.open();
		
		ManagedServiceAccessor msa = 
			new ManagedServiceAccessor(wsa);
		this.accessors.put(sessionID, msa);
		
		return sessionID;
	}
	
	/**
	 * セッションを明示的に閉じる。ただし、セッションはサーバ側で
	 * タイムアウトによって無効になっていることがある。
	 * その場合は、InvalidSessionException が返るが、
	 * 無視してもかまわない。
	 *
	 * @throws InvalidSessionException セッションが無効の場合
	 */
	public void close(String sessionID)
	throws InvalidSessionException
	{
		ManagedServiceAccessor msa = (ManagedServiceAccessor)
			this.accessors.get(sessionID);
		if (msa == null) {
			// エラーメッセージ：セッションが無効です。
			String warnMsg = StringManager.get("E8002");
			throw new InvalidSessionException(warnMsg);
		}
		try {
			msa.getServiceAccessor().close();
		
		} finally {
			// close できたか否かを問わず、このオブジェクトの
			// 管理下からはずす。
			this.accessors.remove(sessionID);
		}
	}

	/**
	 * セッションIDに該当するワークフローサービスアクセッサを
	 * 返す。
	 *
	 * @param sessionID セッションID
	 * @return ワークフローサービスアクセッサ
	 * @throws InvalidSessionException セッションが無効の場合
	 */
	public WorkflowServiceAccessor getServiceAccessor(String sessionID) 
	throws InvalidSessionException {
		ManagedServiceAccessor msa = (ManagedServiceAccessor)
			this.accessors.get(sessionID);
		if (msa == null) {
			// エラーメッセージ：セッションが無効です。
			String errMsg = StringManager.get("E8002")  
				+ "(SessionID=" + sessionID + ")";
			log.error(errMsg);
			throw new InvalidSessionException(errMsg);
		}
		return msa.getServiceAccessor();
	}
	
	/**
	 * タイマーによって一定時間ごとに呼び出される。
	 * タイムアウトされたセッションリソースを解放する。
	 */
	private void timerRaised() {
		// タイムアウトセッションのセッション ID を取得
		Set<String> timeouts = new HashSet<String>();
		for (String sessionID : accessors.keySet()) {
			ManagedServiceAccessor msa 
				= (ManagedServiceAccessor) accessors.get(sessionID);
			long notAccessedTerm 
				= System.currentTimeMillis() - msa.getLastAccessedTime().getTime();
			if (notAccessedTerm >= VALID_TERM) {
				timeouts.add(sessionID);
			}
		}

		// タイムアウトしたセッションに対する処理
		for (String timeoutSessionID : timeouts) {
			// WorkflowServiceAccessor の close メソッドはエンジンへ接続してしまう
		    // ので、ここでは呼び出さない。
		    this.accessors.remove(timeoutSessionID);
		}
		
	}
	
	/**
	 * WorkflowServiceAccessor オブジェクトへの
	 * セッションの最終アクセス時刻を管理するためのクラスである。
	 * WorkflowServiceAccessorManager は、WorkflowServiceAccessor
	 * オブジェクトを直接管理せずに、このクラスのオブジェクトを介して管理する。
	 * 
	 *
	 * @author  ysahara(Argo 21, Corp.)
	 * @version $Revision: 1.2 $
	 * @since   Nautica Workflow 1.0
	 */
	private class ManagedServiceAccessor {
		
		/** セッション最終アクセス時刻 */
		private Date lastAccessedTime;
		
		/** ワークフローサービスアクセッサ */
		private WorkflowServiceAccessor wsa;
		
		private ManagedServiceAccessor(WorkflowServiceAccessor wsa) {
			this.wsa = wsa;
			// 最終アクセス時刻に現在時刻を設定
			this.lastAccessedTime = new Date();
		}
		
		private WorkflowServiceAccessor getServiceAccessor() {
			this.updateLastAccessedTime();
			return this.wsa;
		}
		
		/**
		 * セッションの最終アクセス時刻を取得する。
		 * @return セッション最終アクセス時刻
		 */
		private Date getLastAccessedTime() {
			return this.lastAccessedTime;
		}
		
		/**
	     * 最終アクセス時刻を更新する。
	     */
	    private void updateLastAccessedTime() {
	        this.lastAccessedTime = new Date();
	    }
	}
	
	/**
	 * セッションタイムアウト処理を起動するクラスである。
     * セッション管理で保持しているタイマによって起動される。
	 * 
	 *
	 * @author  ysahara(Argo 21, Corp.)
	 * @version $Revision: 1.2 $
	 * @since   Nautica Workflow 1.0
	 */
	private class TimeoutTimerTask extends TimerTask {
		
		private WorkflowServiceAccessorManager wam;
		
		/**
		 * TimeoutTimerTask を生成する。
		 * 
		 * @param wam ワークフローサービスアクセスマネージャ
		 */
		private TimeoutTimerTask(WorkflowServiceAccessorManager wam) {
			this.wam = wam;
		}
		
		/**
		 * ワークフローサービスアクセスマネージャのタイムアウト
		 * の検知及びセッションリソースの解放処理を呼び出す。
		 * 
		 * @see java.lang.Runnable#run()
		 */
		public void run() {
			this.wam.timerRaised();
		}
	}
}
