/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.common;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.opengion.fukurou.db.ConnectionFactory;
import org.opengion.fukurou.util.Cleanable;
import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.util.LogWriter;
import org.opengion.fukurou.db.DBSimpleTable;

/**
 * Webアプリケーション全体で使用しているオブジェクト類のトータルの管理クラスです。
 *
 * SystemManager は、
 *
 * 		session オブジェクトの管理とアクセス／開放
 *
 * の作業を行います。
 *
 * 上記のクラス(staticメソッド)へのアクセスは、もちろん直接呼び出して
 * 操作することも可能ですが、サーバーのクリーンシャットダウン時やセッションの
 * 開放時、初期化処理など、ある種の統合的なトリガを受けて、関係するクラスに
 * イベントを伝えるようにすることで、Webアプリケーションサーバーとのやり取りを
 * 一元管理する目的で作成されています。
 *
 * @og.group 初期化
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class SystemManager {
	// 3.1.0.0 (2003/03/20) Hashtable を使用している箇所で、非同期でも構わない箇所を、HashMap に置換え。
	private static final Map<String,UserSummary> map = new HashMap<String,UserSummary>( HybsSystem.BUFFER_MIDDLE );

	/** 4.0.0 (2005/01/31) Cleanable インターフェースを実装したオブジェクトを管理します。  */
	private static final List<Cleanable> clearList = new ArrayList<Cleanable>() ;

	/** 4.3.6.2 (2009/04/15) Context終了時のみclear()される Cleanable ブジェクトを管理します。  */
	private static final List<Cleanable> contextClearList = new ArrayList<Cleanable>() ;

	// 4.1.0.0 (2008/01/11) GE12クリア用
	// 4.3.6.6 (2009/05/15) ENGINE_INFOは削除しない
	/** エンジン個別(SYSTEM_ID='個別' KBSAKU='0' CONTXT_PATH='自身')パラメータの一括削除のクエリー	{@value}	*/
	private static final String DEL_SYS = "DELETE FROM GE12 WHERE SYSTEM_ID=? AND KBSAKU='0' AND CONTXT_PATH=? AND PARAM_ID != 'ENGINE_INFO'";

	/**
	 *  デフォルトコンストラクターをprivateにして、
	 *  オブジェクトの生成をさせないようにする。
	 *
	 */
	private SystemManager() {
	}

	/**
	 * session を記録します。
	 *
	 * 管理者権限で、強制ログアウトさせる場合などに、使用します。
	 * Servlet 2.1 では、HttpSessio#getSessionContext() より取り出した
	 * HttpSessionContextのgetSession(java.lang.String sessionId) で
	 * すべての session を取り出せましたが,Deprecated になりました。
	 * セキュリティー上、好ましくない処理ですので,注意して使用してください。
	 * common\session_init.jsp より登録します
	 *
	 * @og.rev 5.5.9.1 (2012/12/07) セッション作成時に、規定のキーでセッションIDを保存しておく。
	 *
	 * @param   session Httpセッション
	 */
	public static void addSession( final HttpSession session ) {
		String sessionID = session.getId();

		UserSummary userInfo = (UserSummary)session.getAttribute( HybsSystem.USERINFO_KEY );
		if( userInfo != null ) {
			synchronized( map ) {
				map.put( sessionID,userInfo );
			}
			session.setAttribute( HybsSystem.SESSION_KEY, sessionID );		// 5.5.9.1 (2012/12/07) セッションIDを保存
		}
	}

	/**
	 * session を削除します。
	 *
	 * 管理者権限で、強制ログアウトさせる場合などに、使用します。
	 * Servlet 2.1 では、HttpSessio#getSessionContext() より取り出した
	 * HttpSessionContextのgetSession(java.lang.String sessionId) で
	 * すべての session を取り出せましたが,Deprecated になりました。
	 * セキュリティー上、好ましくない処理ですので,注意して使用してください。
	 *
	 * @og.rev 5.5.9.1 (2012/12/07) セッション作成時に登録した規定のキーで userInfo を削除します。
	 *
	 * @param   session Httpセッション
	 */
//	public static void removeSession( final String sessionID ) {
	public static void removeSession( final HttpSession session ) {

		String sessionID = (String)session.getAttribute( HybsSystem.SESSION_KEY );	// 5.5.9.1 (2012/12/07) セッションIDを取り出し

		final UserSummary userInfo ;
		synchronized( map ) {
			userInfo = map.remove( sessionID );
		}
		if( userInfo != null ) { userInfo.clear(); }
	}

	/**
	 * すべてのシステムにログイン中のUserSummary オブジェクトを取得します。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 内部ロジック大幅変更
	 *
	 * @param   key ソートするキー項目を指定
	 * @param   direction ソートする方向[true:昇順/false:降順]
	 *
	 * @return	ログイン中のオブジェクト
	 */
	public static UserSummary[] getRunningUserSummary( final String key,final boolean direction ) {
		final UserSummary[] users ;
		synchronized( map ) {
			users = map.values().toArray( new UserSummary[map.size()] );
		}

		if( key != null ) {
			Comparator<UserSummary> comp = getUserSummaryComparator( key,direction );
			Arrays.sort( users,comp );
		}

		return users ;
	}

	/**
	 * システムにログイン中の、すべてのセッション数を、取得します。
	 *
	 * ちなみに、不正なデータが存在した場合は、ここでMapから削除しておきます。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 新規作成
	 *
	 * @return ログイン中の有効なすべてのセッション数
	 */
	public static int getRunningCount() {
		final int rtnSize;
		synchronized( map ) {
			String[] keys = map.keySet().toArray( new String[map.size()] );
			for( int i=0; i<keys.length; i++ ) {
				if( map.get( keys[i] ) == null ) {
					map.remove( keys[i] );
				}
			}
			rtnSize = map.size() ;
		}

		return rtnSize;
	}

	/**
	 * contextDestroyed 時に、すべてのセッションを、invalidate()します。
	 * <del>永続化セッション(SESSIONS.ser)対策</del>
	 * 注意：キャッシュで内部管理していたセッションが、すべて無効化されてしまいます。
	 * よって、内部にセッションを管理しなくなったため、invalidate() もできません。
	 * 不具合が出るかもしれません。
	 *
	 * @og.rev 3.5.2.1 (2003/10/27) 新規作成
	 * @og.rev 4.0.0 (2005/01/31) セッション ⇒ UserSummary に変更
	 */
	static void sessionDestroyed() {
		final UserSummary[] users ;
		synchronized( map ) {
			users = map.values().toArray( new UserSummary[map.size()] );
			map.clear();
		}

		for( int i=0; i<users.length; i++ ) {
			users[i].clear();
		}
		System.out.println( "  [" + users.length + "] Session Destroyed " );
	}

	/**
	 * 初期化したいオブジェクトを登録します。
	 * オブジェクトは、Cleanable インターフェースを実装しておく必要があります。
	 * 実際に、clear() する場合は、ここで登録した全てのオブジェクトの clear()
	 * メソッドが呼び出されます。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 新規作成
	 * @og.rev 4.3.6.2 (2009/04/15) コンテキスト終了時のみのclear()対応
	 *
	 * @param obj インターフェースの実装
	 */
	public static void addCleanable( final Cleanable obj ) {
//		synchronized( clearList ) {
//			clearList.add( obj );
//		}
		addCleanable( obj, false );
	}

	/**
	 * 初期化したいオブジェクトを登録します。
	 * オブジェクトは、Cleanable インターフェースを実装しておく必要があります。
	 * 実際に、clear() する場合は、ここで登録した全てのオブジェクトの clear()
	 * メソッドが呼び出されます。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 新規作成
	 * @og.rev 4.3.6.2 (2009/04/15) コンテキスト終了時のみのclear()対応
	 *
	 * @param obj インターフェースの実装
	 * @param flag trueの場合、コンテキスト停止時のみclear()を呼び出す
	 */
	public static void addCleanable( final Cleanable obj, final boolean flag ) {
		if( flag ) {
			synchronized( contextClearList ) {
				contextClearList.add( obj );
			}
		}
		else {
			 synchronized( clearList ) {
				clearList.add( obj );
			 }
		}
	}

	/**
	 * addCleanable( final Cleanable ) で登録したすべてのオブジェクトを初期化します。
	 * 処理は、Cleanable インターフェースの clear()メソッドを順次呼び出します。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 新規作成
	 * @og.rev 4.3.6.2 (2009/04/15) コンテキスト終了時のみのclear()対応
	 *
	 * @param	flag 完全終了時に、true
	 */
	public static void allClear( final boolean flag ) {
		final Cleanable[] clr ;
		synchronized( clearList ) {
			clr = clearList.toArray( new Cleanable[clearList.size()] );
			if( flag ) { clearList.clear() ; }		// contextDestroyed の場合のみ実行
		}
		// 登録の逆順で処理していきます。
		for( int i=clr.length-1; i>=0; i-- ) {
			clr[i].clear();
		}

		// コンテキスト停止時のみclear()
		if( flag ) {
			final Cleanable[] clr2 ;
			synchronized( contextClearList ) {
				clr2 = contextClearList.toArray( new Cleanable[contextClearList.size()] );
				contextClearList.clear();
			}
			// 登録の逆順で処理していきます。
			for( int i=clr2.length-1; i>=0; i-- ) {
				clr2[i].clear();
			}
		}
	}

	/**
	 * GE12からCONTXT PATHをhost:port/context/で登録している物を削除します。
	 * (web.xmlにTOMCAT_PORTを指定した場合に上記CONTEXT_PATHで登録されます)
	 *
	 * @og.rev 4.1.0.0 (2007/12/26) 新規作成
	 * @og.rev 5.5.4.5 (2012/07/27) 初期起動時のDB接続先は、RESOURCE_DBID とする。
	 */
	protected static void clearGE12() {
		String HOST_URL 		= HybsSystem.sys( "HOST_URL" );
		String RESOURCE_DBID	= HybsSystem.sys( "RESOURCE_DBID" );	// 5.5.4.5 (2012/07/27) 初期起動時のDB接続先
		if( HOST_URL != null && !"**".equals( HOST_URL ) ) {
			Connection connection = null;
			PreparedStatement pstmt = null;
			try {
//				connection = ConnectionFactory.connection( null, null );
				connection = ConnectionFactory.connection( RESOURCE_DBID, null );	// 5.5.4.5 (2012/07/27) 初期起動時のDB接続先は、RESOURCE_DBID とする。
				pstmt = connection.prepareStatement( DEL_SYS );
				pstmt.setString( 1, HybsSystem.sys( "SYSTEM_ID" ) );
				pstmt.setString( 2, HOST_URL );
				int delCnt = pstmt.executeUpdate();
				connection.commit();
				System.out.println( HOST_URL + " DELETE FROM GE12[" + delCnt + "]" );
			} catch (HybsSystemException e) {
				LogWriter.log( e );
			} catch (SQLException e) {
				Closer.rollback( connection );
				LogWriter.log( e );
			}
			finally {
				Closer.stmtClose( pstmt );
				ConnectionFactory.close( connection, null );
			}
		}
	}

	/**
	 * soffice.binをkillする処理をcallします。
	 *
	 * @og.rev 4.3.0.0 (2008/07/18) 新規作成
	 * @og.rev 5.2.2.0 (2010/11/01) 循環参照解消のため、SystemManager から ProcessFactory へ移動
	 */
//	protected static void sofficeKill() {
//		System.out.println("Kill all soffice.bin");
//		ProcessFactory.kill();
//	}

	// deleteGUIAccessInfo() メソッドでしか使用しない、定数宣言
	private static final int C_DEL_SYSTEM_ID		= 0;
	private static final int C_DEL_DYSET			= 1;

	/**
	 * アクセス統計テーブル(GE15)の再編成を行います。
	 * データの保存期間については、システムリソースのACCESS_TOKEI_ALIVE_DAYSで指定します。
	 * データの作成された日時を基準として、上記の期間よりも古いデータは、物理削除されます。
	 * ACCESS_TOKEI_ALIVE_DAYSが指定されていない場合、データの削除は行われません。
	 *
	 * @og.rev 5.0.2.0 (2009/11/01) 新規作成
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 */
	protected static void deleteGUIAccessInfo() {
		String aliveDays = HybsSystem.sys( "ACCESS_TOKEI_ALIVE_DAYS" );
		if( aliveDays == null || aliveDays.length() == 0 ) {
			return;
		}
		String delBaseDate = HybsSystem.getDate( HybsSystem.getDate( "yyyyMMdd" ), -1 * Integer.valueOf( aliveDays ) );

		String[] names = new String[] { "SYSTEM_ID","DYSET" };
		String[] values = new String[names.length];
		values[C_DEL_SYSTEM_ID		] = HybsSystem.sys( "SYSTEM_ID" );
		values[C_DEL_DYSET			] = delBaseDate + "000000";

		String RESOURCE_DBID	= HybsSystem.sys( "RESOURCE_DBID" );	// 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応
		DBSimpleTable dbTable = new DBSimpleTable( names );
		dbTable.setApplicationInfo( null );
		dbTable.setConnectionID( RESOURCE_DBID );	// 5.5.5.1 (2012/08/07)
		dbTable.setTable( "GE15" );
		dbTable.setWhere( "SYSTEM_ID = [SYSTEM_ID] and DYSET <= [DYSET]" );

		boolean okFlag = false;
		try {
			dbTable.startDelete();
			dbTable.execute( values );
			okFlag = true;
		}
		catch (SQLException ex) {
			LogWriter.log( "  アクセス統計テーブル削除時にエラーが発生しました" );
			LogWriter.log( ex.getMessage() );
		}
		finally {
			int cnt = dbTable.close( okFlag );
			System.out.println();
			System.out.println( "  アクセス統計テーブルから、[" + cnt + "]件、削除しました。" );
		}
	}

	/**
	 * UserSummary の指定のキーに対応した項目をソートする Comparator を返します。
	 *
	 * キーは、JNAME,ID,IPADDRESS,LOGINTIME のうちのどれかです。
	 *
	 * @og.rev 3.8.5.3 (2006/08/07) 新規追加
	 *
	 * @param	key	ソートするキー項目を指定
	 * @param	direction	ソートする方向[true:昇順/false:降順]
	 *
	 * @return  指定のキーに対応した項目をソートする Comparator
	 */
	public static Comparator<UserSummary> getUserSummaryComparator( final String key,final boolean direction ) {
		if( "JNAME,ID,ROLES,IPADDRESS,LOGINTIME".indexOf( key ) < 0 ) {
			String errMsg = "ソートキーには、JNAME,ID,ROLES,IPADDRESS,LOGINTIME 以外は指定できません。"
						+ " Key=" + key ;
			throw new HybsSystemException( errMsg );
		}

		Comparator<UserSummary> comp = null;

		if( "JNAME".equals( key ) ) {
			comp = new JNAME_Comparator( direction );
		}
		else if( "ID".equals( key ) ) {
			comp = new ID_Comparator( direction );
		}
		else if( "ROLES".equals( key ) ) {
			comp = new ROLES_Comparator( direction );
		}
		else if( "IPADDRESS".equals( key ) ) {
			comp = new IPADDRESS_Comparator( direction );
		}
		else if( "LOGINTIME".equals( key ) ) {
			comp = new LOGINTIME_Comparator( direction );
		}

		return comp ;
	}

	/**
	 * JNAME で比較する Comparator 内部クラスの定義。
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 */
	private static final class JNAME_Comparator implements Comparator<UserSummary>, Serializable {
		private static final long serialVersionUID = 4000 ;	// 4.0.0 (2006/09/31)
		private final boolean direct ;

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param	direction	ソートの方向[true:昇順/false:降順]
		 */
		public JNAME_Comparator( final boolean direction ) {
			direct = direction;
		}

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param o1 比較対象の最初のオブジェクト
		 * @param o2 比較対象の 2 番目のオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final UserSummary o1, final UserSummary o2 ) {
			String key1 = o1.getJname();
			String key2 = o2.getJname();
			return ( direct ) ? key1.compareTo( key2 ) : key2.compareTo( key1 );
		}
	}

	/**
	 * ID で比較する Comparator 内部クラスの定義。
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 */
	private static final class ID_Comparator implements Comparator<UserSummary>, Serializable {
		private static final long serialVersionUID = 4000 ;	// 4.0.0 (2006/09/31)
		private final boolean direct ;

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param	direction	ソートの方向[true:昇順/false:降順]
		 */
		public ID_Comparator( final boolean direction ) {
			direct = direction;
		}

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param o1 比較対象の最初のオブジェクト
		 * @param o2 比較対象の 2 番目のオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final UserSummary o1, final UserSummary o2 ) {
			String key1 = o1.getUserID();
			String key2 = o2.getUserID();
			return ( direct ) ? key1.compareTo( key2 ) : key2.compareTo( key1 );
		}
	}

	/**
	 * ROLES で比較する Comparator 内部クラスの定義。
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 */
	private static final class ROLES_Comparator implements Comparator<UserSummary>, Serializable {
		private static final long serialVersionUID = 4000 ;	// 4.0.0 (2006/09/31)
		private final boolean direct ;

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param	direction	ソートの方向[true:昇順/false:降順]
		 */
		public ROLES_Comparator( final boolean direction ) {
			direct = direction;
		}

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param o1 比較対象の最初のオブジェクト
		 * @param o2 比較対象の 2 番目のオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final UserSummary o1, final UserSummary o2 ) {
			String key1 = o1.getRoles();
			String key2 = o2.getRoles();
			return ( direct ) ? key1.compareTo( key2 ) : key2.compareTo( key1 );
		}
	}

	/**
	 * IPADDRESS で比較する Comparator 内部クラスの定義。
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 */
	private static final class IPADDRESS_Comparator implements Comparator<UserSummary>, Serializable {
		private static final long serialVersionUID = 4000 ;	// 4.0.0 (2006/09/31)
		private final boolean direct ;

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param	direction	ソートの方向[true:昇順/false:降順]
		 */
		public IPADDRESS_Comparator( final boolean direction ) {
			direct = direction;
		}

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param o1 比較対象の最初のオブジェクト
		 * @param o2 比較対象の 2 番目のオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final UserSummary o1, final UserSummary o2 ) {
			String key1 = o1.getIPAddress();
			String key2 = o2.getIPAddress();
			return ( direct ) ? key1.compareTo( key2 ) : key2.compareTo( key1 );
		}
	}

	/**
	 * LOGINTIME で比較する Comparator 内部クラスの定義。
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 */
	private static final class LOGINTIME_Comparator implements Comparator<UserSummary>, Serializable {
		private static final long serialVersionUID = 4000 ;	// 4.0.0 (2006/09/31)
		private final boolean direct ;

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param	direction	ソートの方向[true:昇順/false:降順]
		 */
		public LOGINTIME_Comparator( final boolean direction ) {
			direct = direction;
		}

		/**
		 * ソートの方向を引数にとるコンストラクタ。
		 *
		 * @og.rev 4.0.0 (2006/09/31) 新規追加
		 *
		 * @param o1 比較対象の最初のオブジェクト
		 * @param o2 比較対象の 2 番目のオブジェクト
		 * @return	最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
		 */
		public int compare( final UserSummary o1, final UserSummary o2 ) {
			long key1 = o1.getLoginTime();
			long key2 = o2.getLoginTime();
			int rtn = (direct) ? 1:-1 ;
			return ( key1 == key2 ) ? 0 : (key1 < key2) ? rtn : -rtn ;
		}
	}
}
