/* $Id: SimpleSessionManager.java,v 1.4 2007/12/11 01:49:38 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.security.simple;

import java.util.Collections;
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.dataaccess.UserManagerDAO;
import jp.co.argo21.nautica.workflow.dataaccess.WorkflowDAOFactory;
import jp.co.argo21.nautica.workflow.engine.DataAccessManager;
import jp.co.argo21.nautica.workflow.engine.LogManager;
import jp.co.argo21.nautica.workflow.omg.WorkflowException;
import jp.co.argo21.nautica.workflow.security.Session;
import jp.co.argo21.nautica.workflow.security.SessionManager;
import jp.co.argo21.nautica.workflow.security.User;
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 nito(Argo 21, Corp.)
 * @author mmanabe(Argo 21, Corp.)
 * @version $Revision: 1.4 $
 * @since Nautica Workflow 1.0
 */
public class SimpleSessionManager implements SessionManager
{
	/** セッションの有効時間：30分（ミリ秒） */
	private static final long VALIDTERM = 1800000L;

	/** ログ出力用オブジェクト */
	private static Logger sLog = LogManager.getSercurityLogger();

	/** 管理下のセッションの集合 */
	private Map<String, Session> sessions = Collections
			.synchronizedMap(new HashMap<String, Session>());

	/**
	 * SimpleSessionManagerを生成する。 このコンストラクタは、SessionManagerFactoryから
	 * リフレクションによって呼び出される。
	 */
	public SimpleSessionManager()
	{
		/*
		 * タイムアウト検知のため、タイマを生成し、タスクをスケジューリングする。 タスクの実行間隔は、セッション有効時間の半分とする。
		 */
		Timer timer = new Timer();
		timer.schedule(new TimeoutTimerTask(this), VALIDTERM, VALIDTERM / 2);
	}

	/**
	 * 引数のユーザIDとパスワードを用いて、認証を行う。 戻り値として、対応付けたセッションIDを返す。
	 * 
	 * @param uid
	 *            ユーザID
	 * @param password
	 *            パスワード
	 * @return セッションID
	 * @throws ConnectionFailedException
	 *             接続認証に失敗した場合
	 * @see jp.co.argo21.nautica.workflow.security.SessionManager#createSession(java.lang.String,
	 *      java.lang.String)
	 */
	public String createSession(String uid, String password)
			throws ConnectionFailedException
	{
		/* User取得 */
		User user = null;
		try {
			WorkflowDAOFactory daoFactory = DataAccessManager.getDAOFactory();
			UserManagerDAO usermanagerDAO = daoFactory.getUserManagerDAO();
			String encoded = SimplePassswordEncoder.encode(uid, password);
			user = usermanagerDAO.findByUserIDAndPasswd(uid, encoded);

		} catch (Exception ex) {
			// ユーザ取得に失敗しました。
			String errMsg = StringManager.get("E1010") + "(UserID=" + uid + ")";
			sLog.error(errMsg);
			throw new ConnectionFailedException(errMsg, ex);
		}

		if (user == null) {
			// ユーザ認証に失敗しました。
			String errMsg = StringManager.get("E1013") + "(UserID=" + uid + ")";
			sLog.error(errMsg);
			throw new ConnectionFailedException(errMsg);
		}

		/* セッション作成（IDの発番とユーザの割付） */
		Session session = new SimpleSession(user);
		String sid = "";
		try {
			sid = session.getID();

		} catch (WorkflowException e) {
			// セッションIDの取得に失敗しました。
			String errMsg = StringManager.get("E1014") + "(UserID=" + uid + ")";
			sLog.error(errMsg);
			throw new ConnectionFailedException(errMsg, e);
		}

		/* セッションを管理下におく */
		sessions.put(sid, session);
		return sid;
	}

	/**
	 * 引数として渡されたセッションIDを検証する。 セッションIDに対応するセッション情報があれば、 最終アクセス時間を更新する。
	 * 対応するセッション情報がなければ、例外を返す。 セッションIDが有効でない場合は、例外とする。
	 * 
	 * @param sid
	 *            セッションID
	 * @throws InvalidSessionException
	 *             指定されたセッションが無効の場合
	 * @see jp.co.argo21.nautica.workflow.security.SessionManager#validateSession(java.lang.String)
	 */
	public void validateSession(String sid) throws InvalidSessionException
	{
		/* セッション取得。なければ例外を投げる。 */
		Session session = (Session) sessions.get(sid);
		if (session == null) {
			// 無効なセッションが指定されました。
			String errMsg = StringManager.get("E1015") + "(SessionID=" + sid
					+ ")";
			sLog.error(errMsg);
			throw new InvalidSessionException(errMsg);
		}

		/* 最終アクセス時間を更新 */
		try {
			/*
			 * Session インタフェースに、updateLastAccessedTime が 定義されていないため、getID()
			 * を実行する。 getID() は、内部で最終アクセス時間を更新している。
			 */
			session.getID();

		} catch (WorkflowException e) {
			// 最終アクセス時間の更新に失敗しました。
			String errMsg = StringManager.get("E1017") + "(SessionID=" + sid
					+ ")";
			sLog.error(errMsg);
			throw new InvalidSessionException(errMsg, e);
		}
	}

	/**
	 * 引数に渡されたセッションIDと、それに対応するセッション情報を無効にする。 セッションIDが有効でない場合は、例外とする。
	 * 
	 * @param sid
	 *            セッションID
	 * @throws InvalidSessionException
	 *             指定されたセッションが無効の場合
	 * @see jp.co.argo21.nautica.workflow.security.SessionManager#invalidateSession(java.lang.String)
	 */
	public void invalidateSession(String sid) throws InvalidSessionException
	{
		/* セッション取得。なければ例外を投げる。 */
		Session session = (Session) sessions.get(sid);
		if (session == null) {
			// 無効なセッションが指定されました。
			String errMsg = StringManager.get("E1015") + "(SessionID=" + sid
					+ ")";
			throw new InvalidSessionException(errMsg);
		}

		/* セッションを管理から外す */
		sessions.remove(sid);
		session = null;
	}

	/**
	 * 引数に渡されたセッションIDに結び付けられている、Userオブジェクトを返す。
	 * 
	 * @param sid
	 *            セッションID
	 * @return セッションIDに結び付けられているUser
	 * @throws InvalidSessionException
	 *             指定されたセッションが無効の場合
	 * @see jp.co.argo21.nautica.workflow.security.SessionManager#getSessionUser(java.lang.String)
	 */
	public User getSessionUser(String sid) throws InvalidSessionException
	{
		/* セッション取得。なければ例外を投げる。 */
		Session session = (Session) sessions.get(sid);
		if (session == null) {
			// 無効なセッションが指定されました。
			String errMsg = StringManager.get("E1015") + "(SessionID=" + sid
					+ ")";
			sLog.error(errMsg);
			throw new InvalidSessionException(errMsg);
		}

		/* ユーザ取得 */
		try {
			User user = session.getUser();
			return user;

		} catch (WorkflowException e) {
			// ユーザの取得に失敗しました。
			String errMsg = StringManager.get("E1010") + "(SessionID=" + sid
					+ ")";
			sLog.error(errMsg);
			throw new InvalidSessionException(errMsg, e);
		}
	}

	/**
	 * 一定時間ごとに、タイムアウトされたセッションを 管理下からはずす。
	 */
	private void timerRaised()
	{
		/* 管理下にあるセッションのうち、有効時間を超過しているものを取得する */
		Set<String> timeouts = new HashSet<String>();
		for (String sid : sessions.keySet()) {
			Session session = (Session) sessions.get(sid);

			try {
				long notAccessedTerm = System.currentTimeMillis()
						- session.getLastAccessedTime().getTime();

				if (notAccessedTerm >= VALIDTERM) {
					timeouts.add(sid);
				}

			} catch (WorkflowException e) {
				// 最終アクセス時間の取得に失敗しました。
				String warnMsg = StringManager.get("E1016");
				sLog.error(warnMsg, e);
			}
		}

		/* 有効時間を超過したセッションを無効化する */
		for (String timeoutSID : timeouts) {
			try {
				invalidateSession(timeoutSID);

			} catch (InvalidSessionException e) {
				/* 例外は投げず、ログ出力のみ */
				sLog.warn(e.getMessage(), e);
			}
		}
	}

	/**
	 * セッションタイムアウト処理を起動するクラス。 SimpleSessionManager が持つタイマによって起動される。
	 * 
	 * @author mmanabe(Argo 21, Corp.)
	 * @version $Revision: 1.4 $
	 * @since Nautica Workflow 1.0
	 */
	private class TimeoutTimerTask extends TimerTask
	{

		private SimpleSessionManager ssm;

		/**
		 * TimeoutTimerTask を生成する。
		 * 
		 * @param ssm
		 *            SimpleSessionManager オブジェクト
		 */
		TimeoutTimerTask(SimpleSessionManager ssm)
		{
			this.ssm = ssm;
		}

		/**
		 * セッションタイムアウトの検知。 実際の処理は SimpleSessionManager#timerRaised メソッドで行われる。
		 * 
		 * @see java.util.TimerTask#run()
		 */
		public void run()
		{
			ssm.timerRaised();
		}
	}
}