/*
 * 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.resource;

import java.text.MessageFormat;
import java.util.List;

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.common.SystemManager;
import org.opengion.fukurou.util.LogWriter;
import org.opengion.fukurou.util.Cleanable;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.ApplicationInfo;
import org.opengion.fukurou.db.DBUtil;

/**
 * systemId に対応したユーザー情報を作成するファクトリクラスです。
 *
 * UserInfoオブジェクトは，キャッシュせずに、要求都度、データベースを検索します。
 * これは、ユーザー登録が、他システムより行われる可能性を考慮している為です。
 * ユーザーオブジェクトの要求は、基本的にログイン時のみで、その後セッションに
 * キープされます。
 *
 * 検索するカラムには、必ず、USERID,LANG,NAME,ROLES,DROLES がこの順番で含まれており、
 * 絞込み条件(?パラメータ)として、SYSTEM_ID,USERID がこの順番で指定される必要があります。
 * (カラム名は関係ありません。並び順と意味が重要です。)
 * また、検索順(ORDER BY)は、優先順位の低い順に検索してください。使用するのは、一番最後に
 * 検索された行を使用します。
 * ユーザーリソースは、RESOURCE_USER_DBID で指定のデータベースから取得します。
 * 未定義の場合は、RESOURCE_DBID から、それも未定義の場合は デフォルトの接続先を
 * 使用します。
 *
 * SYSTEM_ID='**' は、共通リソースです(ROLESも共通に設定する必要があります。)。
 * これは、システム間で共通に使用されるリソース情報を登録しておきます。
 * SYSTEM_ID は、指定のシステムIDと**を検索対象にします。**は、全システム共通の
 * 指定のシステムIDと**と両方存在する場合は、指定のシステムIDが優先されます。
 *
 * ver4 では、デフォルトロールという考え方がなくなりましたので、画面のロールに、
 * (*)を明示的に追加し、RWMODE を指定する必要があります。
 *
 * @og.rev 4.0.0 (2004/12/31) 新規作成
 * @og.group リソース管理
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class UserInfoFactory {

	private static final String SYSTEM_ID = HybsSystem.sys( "SYSTEM_ID" );

	// ユーザーリソースの接続先を、取得します。
	private static String dbid = StringUtil.nval(
								HybsSystem.sys( "RESOURCE_USER_DBID" ) ,
								HybsSystem.sys( "RESOURCE_DBID" )
							) ;

	// ユーザーリソースのキー指定読み込みのクエリー
	private static String query			= HybsSystem.sys( "RESOURCE_USER_SQL" );
	private static String queryRole		= HybsSystem.sys( "RESOURCE_USER_ROLE_SQL" );

	// 5.2.0.0 (2010/09/01) LDAP対応
	private static String srcType		= HybsSystem.sys( "RESOURCE_USER_SRC_TYPE" );
	private static String[] ldapClm		= StringUtil.csv2Array( HybsSystem.sys( "RESOURCE_USER_LDAP_CLM" ) );
	private static String ldapFilter	= HybsSystem.sys( "RESOURCE_USER_LDAP_FILTER" );
	private static String ldapRoleFilter= HybsSystem.sys( "RESOURCE_USER_ROLE_LDAP_FILTER" );

	private static String searchScope	= HybsSystem.sys( "LDAP_SEARCH_SCOPE" );
	private static String initctx 		= HybsSystem.sys( "LDAP_INITIAL_CONTEXT_FACTORY" );
	private static String providerURL 	= HybsSystem.sys( "LDAP_PROVIDER_URL" );
	private static String entrydn 		= HybsSystem.sys( "LDAP_ENTRYDN" );
	private static String password 		= HybsSystem.sys( "LDAP_PASSWORD" );
	private static String searchbase	= HybsSystem.sys( "LDAP_SEARCH_BASE" );

	/** コネクションにアプリケーション情報を追記するかどうか指定 */
	public final static boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;

	// 4.0.0 (2005/01/31) Cleanable インターフェースによる初期化処理
	static {
		Cleanable clr = new Cleanable() {
			public void clear() {
				UserInfoFactory.clear();
			}
		};

		SystemManager.addCleanable( clr );
	}

	private final static Object lock = new Object();

	// GE20 ユーザーロール情報永続化テーブル 読み込み用SQL
	// 4.3.4.0 (2008/12/01) ROLE='*'も検索できるようにする
//	private final static String QUERY_GE20 = "select PARAM_ID,PARAM from GE20 where SYSTEM_ID = ? and USERID = ? and ROLES = ? ";
	private final static String QUERY_GE20 = "select PARAM_ID,PARAM from GE20 where SYSTEM_ID = ? and USERID = ? and ROLES in ( ?, '*') and FGJ = '1' ORDER BY ROLES";

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

	/**
	 * UserInfo オブジェクトを取得します。
	 *
	 * UserInfoオブジェクトは，キャッシュせずに、要求都度、データベースを検索します。
	 * これは、ユーザー登録が、他システムより行われる可能性を考慮している為です。
	 * ユーザーオブジェクトの要求は、基本的にログイン時のみで、その後セッションに
	 * キープされます。
	 *
	 * @og.rev 3.7.0.4 (2005/03/18) ゲストログイン機能追加
	 * @og.rev 4.0.0.0 (2007/10/31) ロール指定でのログイン機能追加
	 * @og.rev 4.3.4.0 (2008/12/01) GE20(ユーザー定数)へ登録するかのフラグへの対応
	 * @og.rev 4.4.0.0 (2009/08/02) データロール対応
	 * @og.rev 5.2.0.0 (2010/09/01) LDAP対応
	 *
	 * @param   userID    ユーザーID
	 * @param   ipAddress ログイン端末のIPアドレス
	 * @param   roles データロール
	 * @return  UserInfo  オブジェクト
	 */
	public static UserInfo newInstance( final String userID,final String ipAddress,final String roles ) {
		// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfo オブジェクトを設定
		ApplicationInfo appInfo = null ;
		if( USE_DB_APPLICATION_INFO ) {
			appInfo = new ApplicationInfo();
			// ユーザーID,IPアドレス,ホスト名
			appInfo.setClientInfo( userID,ipAddress,null );
			// 画面ID,操作,プログラムID
			appInfo.setModuleInfo( "UserInfoFactory",null,"newInstance" );
		}

//		String[] args;
//		String[][] vals;

//		if( roles == null || roles.length() == 0 ) {
//			args = new String[] { SYSTEM_ID,userID };
//			synchronized( lock ) {
//				vals = DBUtil.dbExecute( query,args,appInfo,dbid );
//			}
//		}
//		// 4.0.0.0 (2007/10/31)
//		else {
//			args = new String[] { SYSTEM_ID,userID,roles };
//			synchronized( lock ) {
//				vals = DBUtil.dbExecute( queryRole,args,appInfo,dbid );
//			}
//		}

		String[][] vals;
		if( "LDAP".equalsIgnoreCase( srcType ) ) {
			vals = getValsByLdap( userID, roles );
		}
		else {
			vals = getVals( userID, roles, appInfo );
		}

		final UserInfo info ;
		int len = vals.length ;	// システムID ** を含む。
		if( len >= 1 && vals[0].length >= 5 ) {
			// システムIDでソートされる。SYSTEM_ID="**"は最初に現れるので、最後を取得
			info = new UserInfo(
								userID		,			// userID
								vals[len-1][1]	,		// lang
								vals[len-1][2]	,		// jname
								vals[len-1][3]	,		// roles
								vals[len-1][4]	,		// droles // 4.4.0.0 (2009/08/02)
								SYSTEM_ID		,		// systemId
								ipAddress		,		// ipAddress
								appInfo			) ;		// ApplicationInfo
		}
		else {
			String errMsg = "UserInfo のデータ(USERID,LANG,NAME,ROLES,DROLES)が取得できません。"
						+ " Key [" + userID + "]"
						+ " SQL [" + query + "]" ;
			LogWriter.log( errMsg );
			throw new HybsSystemException( errMsg );
		}

		// GE20 ユーザーロール情報永続化テーブル読み込み
		String[] argsGe20 = new String[] { SYSTEM_ID,userID,vals[len-1][3] };
		String[][] valsGe20 ;
		synchronized( lock ) {
			valsGe20 = DBUtil.dbExecute( QUERY_GE20,argsGe20,appInfo,dbid );
		}
		for( int i=0; i<valsGe20.length; i++ ) {
//			info.setAttribute( valsGe20[i][0], valsGe20[i][1] );
			// 4.3.4.0 (2008/12/01) GE20から読み込んでいるので、当然保存しない
			info.setAttribute( valsGe20[i][0], valsGe20[i][1], false );
		}

		return info ;
	}

	/**
	 * UserInfoFactoryをクリアします。
	 * 
	 * @og.rev 5.2.0.0 (2010/09/01) LDAP対応
	 *
	 */
	public static void clear() {
		synchronized( lock ) {
			dbid = StringUtil.nval(
									HybsSystem.sys( "RESOURCE_USER_DBID" ) ,
									HybsSystem.sys( "RESOURCE_DBID" )
								) ;
			query = HybsSystem.sys( "RESOURCE_USER_SQL" );
			queryRole = HybsSystem.sys( "RESOURCE_USER_ROLE_SQL" );

			// 5.2.0.0 (2010/09/01) LDAP対応
			srcType			= HybsSystem.sys( "RESOURCE_USER_SRC_TYPE" );
			ldapClm			= StringUtil.csv2Array( HybsSystem.sys( "RESOURCE_USER_LDAP_CLM" ) );
			ldapFilter		= HybsSystem.sys( "RESOURCE_USER_LDAP_FILTER" );
			ldapRoleFilter	= HybsSystem.sys( "RESOURCE_USER_ROLE_LDAP_FILTER" );

			searchScope		= HybsSystem.sys( "LDAP_SEARCH_SCOPE" );
			initctx 		= HybsSystem.sys( "LDAP_INITIAL_CONTEXT_FACTORY" );
			providerURL 	= HybsSystem.sys( "LDAP_PROVIDER_URL" );
			entrydn 		= HybsSystem.sys( "LDAP_ENTRYDN" );
			password 		= HybsSystem.sys( "LDAP_PASSWORD" );
			searchbase		= HybsSystem.sys( "LDAP_SEARCH_BASE" );
		}
	}

	/**
	 * DBからユーザーリソースの情報を取得します。
	 * 
	 * @og.rev 5.2.0.0 (2010/09/01) 新規作成
	 * 
	 * @param userId ユーザーID
	 * @param roles ロール
	 * @param appInfo DB接続情報
	 * @return ユーザーリソース情報
	 */
	private static String[][] getVals( final String userId, final String roles, final ApplicationInfo appInfo ) {
		String[] args;
		String[][] rtn = null;

		if( roles == null || roles.length() == 0 ) {
			args = new String[] { SYSTEM_ID,userId };
			synchronized( lock ) {
				rtn = DBUtil.dbExecute( query,args,appInfo,dbid );
			}
		}
		// 4.0.0.0 (2007/10/31)
		else {
			args = new String[] { SYSTEM_ID,userId,roles };
			synchronized( lock ) {
				rtn = DBUtil.dbExecute( queryRole,args,appInfo,dbid );
			}
		}
		
		return rtn;
	}

	/**
	 * LDAPからユーザーリソースの情報を取得します。
	 * 
	 * @og.rev 5.2.0.0 (2010/09/01) 新規作成
	 * 
	 * @param userId ユーザーID
	 * @param roles ロール
	 * @return ユーザーリソース情報
	 */
	private static String[][] getValsByLdap( final String userId, final String roles ) {
		LDAPSearch serch = new LDAPSearch();
		serch.setSearchScope( searchScope ) ;
		serch.setInitctx( initctx ) ;
		serch.setProviderURL( providerURL ) ;
		serch.setSearchbase( searchbase ) ;
		if( entrydn != null  ) { serch.setEntrydn( entrydn ) ; }
		if( password != null ) { serch.setPassword( password ) ; }
		serch.setAttributes( ldapClm ) ;
		serch.init();

		String filter = ( roles == null || roles.length() == 0 ) ? ldapFilter : ldapRoleFilter;
		String[] args = ( roles == null || roles.length() == 0 ) ? new String[] { SYSTEM_ID,userId } : new String[] { SYSTEM_ID,userId,roles };
		filter = MessageFormat.format( filter,(Object[])args );

		List<String[]> list = serch.search( filter );

		String[][] rtn = null;
		if( list.size() > 0 ) {
			rtn = new String[1][];
			rtn[0] = list.get( 0 );
			rtn[0][1] = StringUtil.nval( rtn[0][1], "ja" );			// 言語のデフォルト値は、'ja'
			rtn[0][2] = StringUtil.nval( rtn[0][2], rtn[0][0] );	// 名称のデフォルト値は、ユーザーID
			rtn[0][3] = StringUtil.nval( rtn[0][3], ldapClm[3] );	// ロールズのデフォルトは、ロールに設定された項目名
			rtn[0][4] = StringUtil.nval( rtn[0][4], "" );
		}

		return rtn;
	}
}
