/*
 * 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.system.LogWriter;
import org.opengion.fukurou.util.Cleanable;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.db.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.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" );

	private static final Object LOCK = new Object();												// 6.4.1.1 (2016/01/16) lock → LOCK  refactoring

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

	// 4.0.0 (2005/01/31) Cleanable インターフェースによる初期化処理
	static {
		final Cleanable clr = new Cleanable() {
			/**
			 * 初期化(クリア)します。
			 * 主に、キャッシュクリアで利用します。
			 */
			public void clear() {
				UserInfoFactory.clear();
			}
		};

		SystemManager.addCleanable( clr );
	}

	/**
	 *  デフォルトコンストラクターを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対応
	 * @og.rev 5.3.6.0 (2011/06/01) GE20の読み込みをUserInfo内に移動
	 *
	 * @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[][] vals;
		if( "LDAP".equalsIgnoreCase( srcType ) ) {
			vals = getValsByLdap( userID, roles );
		}
		else {
			vals = getVals( userID, roles, appInfo );
		}

		final UserInfo info ;
		final 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 {
			final String errMsg = "UserInfo のデータ(USERID,LANG,NAME,ROLES,DROLES)が取得できません。"
						+ " Key [" + userID + "]"
						+ " SQL [" + query + "]" ;
			LogWriter.log( errMsg );
			throw new HybsSystemException( errMsg );
		}

		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;

		if( roles == null || roles.isEmpty() ) {
			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 ) {
		final 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();

		// 6.4.2.1 (2016/02/05) PMD refactoring. Useless parentheses.
		String filter = roles == null || roles.isEmpty() ? ldapFilter : ldapRoleFilter;
		final String[] args = roles == null || roles.isEmpty() ? new String[] { SYSTEM_ID,userId } : new String[] { SYSTEM_ID,userId,roles };
		filter = MessageFormat.format( filter,(Object[])args );

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

		String[][] rtn = null;
		if( !list.isEmpty() ) {			// 6.1.1.0 (2015/01/17) refactoring
			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;
	}
}
