/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/
//
//
//	AD同期処理クラス　AD情報取得クラス
//			変更履歴
//				2004.01.08	新規作成	            二條智文
//
package jp.co.ntt.lms.xmo.Ad;

import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;


import jp.co.ntt.lms.xmo.util.DebugLog;
import jp.co.ntt.lms.xmo.XMOPropertyDef;
import jp.co.ntt.lms.xmo.People.*;
import jp.co.ntt.lms.xmo.Groups.*;
import jp.co.ntt.lms.xmo.LumpInsert.*;

/**
 * AD同期処理 AD情報取得クラス。<BR>
 * @author T.Nijou
 * @version 2.0.1.0 2004/01/08
 */
public class ADCorpSearch {

	// 同期設定データを保持します。
	ADCorpSetupData mobjSetup;

	/**
	 * コンストラクタです。
	 * 同期設定データを設定します。
	 * @param objSetup
	 */
	public ADCorpSearch( ADCorpSetupData objSetup ) {

		mobjSetup = objSetup;
	}

	/**
	 * ADからデータを取得して、AD同期ツリーを生成して返します。
	 * @return ADCorpNode AD同期ツリーのルートノード
	 * @exception ADCorpException
	 */
	public ADCorpNode getADInfo()
		throws ADCorpException
	{
		if( mobjSetup == null ) {
			// 内部エラー
			throw new ADCorpException( ADCorpMessage.INNER_ERR );
		}

		NamingEnumeration objNaming = search();

		// マッピング処理
		ADCorpNode objNode = mapping(objNaming, mobjSetup.getRootDN());

		return objNode;
	}

	/**
	 * 認証処理を行います。
	 * @return String 認証に成功したユーザのログインIDを返します。
	 *                認証に失敗した場合はnullを返します。
	 */
	public String certify()
		throws ADCorpException
	{
		if( mobjSetup == null ) {
			// 内部エラー
			throw new ADCorpException( ADCorpMessage.INNER_ERR );
		}

		String strLoginID = null;
		try {
			NamingEnumeration objNaming = search();


			// マッピング処理
			ADCorpNode objNode = mapping(objNaming, null);

			// マッピング処理結果がNULL 又は Groupの場合は認証失敗
			if( objNode == null || objNode instanceof ADCorpGroup ) {
				return null;
			}

			ADCorpPeople objPeople = (ADCorpPeople)objNode;

			// XMS2システムのユーザ存在チェックをし、いなかったら親エントリの
			// DN情報をもったグループ直下にユーザを作成する。
			// 親エントリのDN情報をもったグループが存在しない場合はドメインルート直下に
			// 作成する。
			// ドメインルートも存在しない場合は認証失敗とする。
			strLoginID = searchAndInsert( objPeople );

		}
		catch( ADCorpException ade ) {

			// システムエラー、DBエラー、内部エラーは例外をスローする。
			String strMessageID = ade.getMsgNum();
			if( strMessageID.equals( ADCorpMessage.SYSTEM_ERR ) ||
				strMessageID.equals( ADCorpMessage.DB_ERR ) ||
				strMessageID.equals( ADCorpMessage.INNER_ERR ) )
			{
				throw ade;
			}
		}

		return strLoginID;
	}

	/**
	 * LDAPを使用してADからエントリ一覧を取得します。
	 * @return NamingEnumeration 検索結果エントリ一覧
	 * @exception ADCorpConnectException AD接続エラー
	 * @exception ADCorpSearchException AD検索エラー
	 */
	public NamingEnumeration search()
		throws ADCorpConnectException, ADCorpSearchException
	{
		try{

			// @LOG >info >5 AD同期処理:ADへの検索処理を開始します。:ドメイン設定番号:n
			DebugLog.write( this.getClass(),
					"AD同期処理:ADへの検索処理を開始します。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
					DebugLog.MID);

			DirContext ctxLDAP;

			Hashtable env = new Hashtable();
			env.put(Context.SECURITY_AUTHENTICATION, "simple");
			env.put(Context.SECURITY_PRINCIPAL, mobjSetup.getADUser());
			env.put(Context.SECURITY_CREDENTIALS, mobjSetup.getADPass());
			env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
			env.put(Context.PROVIDER_URL,"ldap://" + mobjSetup.getADHost() + ":" + mobjSetup.getADPort());

			// バイナリ属性の指定
			if( mobjSetup.getBinaryAttribute() != null && !mobjSetup.getBinaryAttribute().equals("") ) {
				env.put("java.naming.ldap.attributes.binary",mobjSetup.getBinaryAttribute());
			}

			ctxLDAP = new InitialDirContext(env);
			SearchControls constraints = new SearchControls();
			constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);

			NamingEnumeration results = ctxLDAP.search(mobjSetup.getRootDN(),mobjSetup.getADFilter(), constraints);

			// @LOG >info >5 AD同期処理:ADへの検索処理が終了しました。:ドメイン設定番号:n
			DebugLog.write( this.getClass(),
					"AD同期処理:ADへの検索処理が終了しました。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
					DebugLog.MID);

			return results;
		}
		catch( CommunicationException ce ) {
			// @LOG >err >1 AD同期処理:ADへの接続に失敗しました。:ドメイン設定番号:"n
			DebugLog.write( this.getClass(),
					"AD同期処理:ADへの接続に失敗しました。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
					DebugLog.HIGHT);
			DebugLog.write( this.getClass(), ce, DebugLog.HIGHT );

			// ADホスト、ADポートが不正
			ADCorpConnectException ace = new ADCorpConnectException();
			throw ace;
		}
		catch( AuthenticationException ae ) {
			// @LOG >err >1 AD同期処理:ADへの接続に失敗しました。:ドメイン設定番号:"n
			DebugLog.write( this.getClass(),
					"AD同期処理:ADへの接続に失敗しました。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
					DebugLog.HIGHT);
			DebugLog.write( this.getClass(), ae, DebugLog.HIGHT );

			// ADユーザ、ADパスワードが不正
			ADCorpConnectException ace = new ADCorpConnectException();
			throw ace;
		}
		catch( NameNotFoundException nfe ) {
			// @LOG >err >1 AD同期処理:ADへの検索に失敗しました。:ドメイン設定番号:"n
			DebugLog.write( this.getClass(),
					"AD同期処理:ADへの検索に失敗しました。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
					DebugLog.HIGHT);
			DebugLog.write( this.getClass(), nfe, DebugLog.HIGHT );

			// 検索ルートが不正
			ADCorpSearchException ase = new ADCorpSearchException();
			throw ase;
		}
		catch( InvalidSearchFilterException ife ) {
			// @LOG >err >1 AD同期処理:ADへの検索に失敗しました。:ドメイン設定番号:"n
			DebugLog.write( this.getClass(),
					"AD同期処理:ADへの検索に失敗しました。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
					DebugLog.HIGHT);
			DebugLog.write( this.getClass(), ife, DebugLog.HIGHT );

			// 検索フィルタが不正
			ADCorpSearchException ase = new ADCorpSearchException();
			throw ase;
		}
		catch(NamingException ne) {
			// @LOG >err >1 AD同期処理:ADへの接続に失敗しました。:ドメイン設定番号:"n
			DebugLog.write( this.getClass(),
					"AD同期処理:ADへの接続に失敗しました。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
					DebugLog.HIGHT);
			DebugLog.write( this.getClass(), ne, DebugLog.HIGHT );

			// その他のエラーは接続エラーとする。
			ADCorpConnectException ace = new ADCorpConnectException();
			throw ace;
		}
	}

	/**
	 * マッピング処理を行います。
	 * @return ADCorpNode　ツリーのルートノード
	 * @exception ADCorpException
	 */
	private ADCorpNode mapping(NamingEnumeration objNaming, String strRootDN)
		throws ADCorpException
	{
		if( objNaming == null ) {
			// 内部エラー
			throw new ADCorpException( ADCorpMessage.INNER_ERR );
		}

		// @LOG >info >5 AD同期処理:マッピング処理を開始します。:ドメイン設定番号:n
		DebugLog.write( this.getClass(),
				"AD同期処理:マッピング処理を開始します。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
				DebugLog.MID);


		// DNを検索順に保持するVector
		Vector vecDN = new Vector();
		// DNをキーとして、マッピングオブジェクトを保持する。
		Hashtable htbMap = new Hashtable();
		try{

			// カウンタ
			int intCounter = 0;

			while( objNaming.hasMore() ) {
				// カウンタプラス
				intCounter++;
				SearchResult si = (SearchResult)objNaming.next();
				Attributes attrs = si.getAttributes();
				if (attrs == null) {
				}
				else {
					Attribute attr = attrs.get(mobjSetup.getUserObjKey());
					if( attr == null ) {
						// グループ
						ADCorpGroup objGroup = new ADCorpGroup( si, mobjSetup, intCounter );
						htbMap.put( objGroup.getDN(), objGroup );
						vecDN.add( objGroup.getDN() );
					}
					else {
						// ユーザ
						ADCorpPeople objPeople = new ADCorpPeople( si, mobjSetup, intCounter );
						htbMap.put( objPeople.getDN(), objPeople );
						vecDN.add( objPeople.getDN() );
					}
				}
			}
		}
		catch( PartialResultException pe ) {
		}
		catch(NamingException ne) {
			// @LOG >err >1 AD同期処理:マッピング処理に失敗しました。:ドメイン設定番号:n
			DebugLog.write( this.getClass(),
				"AD同期処理:マッピング処理に失敗しました。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
				DebugLog.HIGHT);
			DebugLog.write( this.getClass(), ne, DebugLog.HIGHT );

			// 検索エラー
			ADCorpSearchException ase = new ADCorpSearchException();
			throw ase;
		}

		// ルートノード
		ADCorpNode objRoot = null;

		// ツリーマッピング
		for( int i = 0; i < vecDN.size(); i++ ) {
			String strDN = (String)vecDN.get(i);
			String strParentDN = getParentDN(strDN);

			ADCorpNode objSelf = (ADCorpNode)htbMap.get(strDN);
			ADCorpNode objParent = (ADCorpNode)htbMap.get(strParentDN);

			// 同期ルートを決定する場合はDN（識別名）の大文字小文字を区別しない
			// 検索で大文字小文字を区別しないため
			if( objSelf.getDN().equalsIgnoreCase(strRootDN) ) {
				objRoot = objSelf;
			}
			else if( objParent == null && strRootDN == null ) {
				// 親がNULLでルート指定がない場合はこのノードをルートとする。
				objRoot = objSelf;
			}
			else if( objParent != null ) {
				objParent.addChild( objSelf );
			}
			else {
				// @LOG >debug >10
				DebugLog.write(this.getClass(), "未設定エントリ: " + strDN, DebugLog.ROW );
			}

			// グループの場合はメンバー設定
			if( objSelf instanceof ADCorpGroup ) {
				((ADCorpGroup)objSelf).setMember( htbMap );
			}
		}

		// @LOG >ifno >5 AD同期処理:マッピング処理が終了しました。:ドメイン設定番号:n
		DebugLog.write( this.getClass(),
				"AD同期処理:マッピング処理が終了しました。:ドメイン設定番号:" + mobjSetup.getDomainNo(),
				DebugLog.MID);

		return objRoot;
	}


	/**
	 * XMS2システムのユーザ存在チェックをし、いなかったら親エントリの
	 * DN情報をもったグループ直下にユーザを作成する。
	 * 親エントリのDN情報をもったグループが存在しない場合はドメインルート直下に
	 * 作成する。
	 * ドメインルートも存在しない場合は認証失敗とする。
	 * @param objPeople マッピング情報
	 * @return String ログインID　認証失敗時はnull
	 * @exception ADCorpException
	 */
	private String searchAndInsert( ADCorpPeople objADPeople )
		throws ADCorpException
	{
		String strLoginID = null;

		try {
			PeopleClient objPeople = selectPeople( objADPeople.getOid() );

			if( objPeople.next() ) {
				// 検索結果取得
				strLoginID = objPeople.getLoginID();
			}
			else {
				// 検索結果が取得出来ない場合は新規にユーザを作成する。
				strLoginID = insertPeople( objADPeople );
			}
		}
		catch( ADCorpException ae ) {
			throw ae;
		}
		catch(Exception ex ) {
			// システムエラー
			throw new ADCorpException( ADCorpMessage.SYSTEM_ERR );
		}

		return strLoginID;
	}

	/**
	 * XMS2にユーザを新規作成します。
	 * ADユーザの親エントリのDNを持つグループ直下に作成します。
	 * 上記グループが見つからない場合はドメインルートグループ直下に作成します。
	 * ドメインルートグループが存在しない場合は作成しません。
	 * @param objADPeople ユーザエントリ情報
	 * @return String 作成したユーザのログインID 作成されなかった場合はnullを返す。
	 * @exception ADCorpException
	 */
	private String insertPeople( ADCorpPeople objADPeople )
		throws ADCorpException
	{
		String strLoginID = null;

		try {
			// 親エントリのDNで検索
			GroupsClient objGroup = selectGroup( getParentDN(objADPeople.getDN()) );
			if( !objGroup.next() ) {
				// 検索結果がない場合はroot_dnで検索
				objGroup = selectGroup( mobjSetup.getRootDN() );

				if( !objGroup.next() ) {
					// ドメインルートも存在しない場合は作成しない
					return null;
				}
			}

			// DB登録設定（共通同期処理を呼び出す)
			LumpInsertClient objLump = new LumpInsertClient();

			objLump.setEncode("Windows-31J");	// エンコードをWindows-31Jに設定			
			objLump.setLumpRootGroup(objGroup.getGroupID());
			objLump.setLumpXML(objADPeople.getXML());	// 共通同期XML設定

			// 登録処理
			if( !objLump.insert() ) {
				if(objLump.isErr()) {
					// システムエラーまたはデータベースエラー
					String strMessageID  = objLump.getMessageID();
					if( strMessageID.equals( LumpInsertMessage.DB_ERR ) ) {
						// データベースエラー
						throw new ADCorpException( ADCorpMessage.DB_ERR );
					}
					else {
						// システムエラー
						throw new ADCorpException( ADCorpMessage.SYSTEM_ERR );
					}
				}
				else {
					// 警告
					String strMessageID  = objLump.getMessageID();
					// 登録に失敗したグループ・ユーザが存在する
					if( strMessageID.equals( LumpInsertMessage.INSERT_WAR ) ) {
						// 認証失敗
						return null;
					}
					else {
						// その他は内部エラー
						throw new ADCorpException( ADCorpMessage.INNER_ERR );
					}
				}
			}

			// 登録結果を取得
			PeopleClient objPeople = selectPeople( objADPeople.getOid() );

			if( objPeople.next() ) {
				strLoginID = objPeople.getLoginID();
			}

		}
		catch( ADCorpException ae ) {
			throw ae;
		}
		catch(Exception ex ) {
			// システムエラー
			throw new ADCorpException( ADCorpMessage.SYSTEM_ERR );
		}

		return strLoginID;
	}

	/**
	 * ADDNを元にグループ検索
	 * @return GroupsClient 検索結果
	 */
	private GroupsClient selectGroup( String strDN )
		throws ADCorpException
	{
		GroupsClient objGroup = null;
		try {
			objGroup = new GroupsClient();
			objGroup.setWhereAdDn( strDN );

			if( !objGroup.select() ) {
				// 検索失敗時
				String strMessageID = objGroup.getMessageID();
				if( strMessageID == null ) {
					// メッセージが取得出来ない場合はシステムエラー
					throw new ADCorpException( ADCorpMessage.SYSTEM_ERR );
				}
				else {
					if( strMessageID.equals( GroupsMessage.DB_ERR ) ) {
						// DBエラー
						throw new ADCorpException(ADCorpMessage.DB_ERR);
					}
					else {
						// システムエラー
						throw new ADCorpException(ADCorpMessage.SYSTEM_ERR);
					}
				}
			}

		}
		catch( ADCorpException ae ) {
			throw ae;
		}
		catch(Exception ex ) {
			// システムエラー
			throw new ADCorpException( ADCorpMessage.SYSTEM_ERR );
		}

		return objGroup;
	}

	/**
	 * オブジェクトIDを元にユーザ検索
	 * @return PeopleClient 検索結果
	 */
	private PeopleClient selectPeople( String strOid )
		throws ADCorpException
	{
		PeopleClient objPeople = null;

		try {
			objPeople = new PeopleClient();

			// 種別とオブジェクトIDで検索
			objPeople.setWhereUserType( XMOPropertyDef.AD_TYPE );
			objPeople.setWhereOid( strOid );

			if( !objPeople.select() ) {
				// 検索失敗時
				String strMessageID = objPeople.getMessageID();
				if( strMessageID == null ) {
					// メッセージが取得出来ない場合はシステムエラー
					throw new ADCorpException( ADCorpMessage.SYSTEM_ERR );
				}
				else {
					if( strMessageID.equals( PeopleMessage.DB_ERR ) ) {
						// DBエラー
						throw new ADCorpException(ADCorpMessage.DB_ERR);
					}
					else {
						// システムエラー
						throw new ADCorpException(ADCorpMessage.SYSTEM_ERR);
					}
				}
			}
		}
		catch( ADCorpException ae ) {
			throw ae;
		}
		catch(Exception ex ) {
			// システムエラー
			throw new ADCorpException( ADCorpMessage.SYSTEM_ERR );
		}

		return objPeople;
	}

	/**
	 * DNから親DNを取り出します。
	 * <PRE>
	 *    ex) dc=lms,dc=ntt,dc=co,dc=jp
	 *        の親DNは
	 *        dc=ntt,dc=co,dc=jp
	 * </PRE>
	 */
	static private String getParentDN(String strDN ) {
		if( strDN == null || strDN.indexOf(',') < 0 ) {
			return strDN;
		}

		return strDN.substring( strDN.indexOf(',') + 1, strDN.length() );

	}

// ------------- デバック-------------------------------------------------------
	private void debug(NamingEnumeration results) {
			try{
				while (results != null && results.hasMore()) {
					SearchResult si = (SearchResult)results.next();
					Attributes attrs = si.getAttributes();
					if (attrs == null) {
					} else {
						for (NamingEnumeration ae = attrs.getAll();ae.hasMoreElements();)
						{
							Attribute attr = (Attribute)ae.next();
							String attrId = attr.getID();
							for (Enumeration vals = attr.getAll();vals.hasMoreElements();)
							{
								if ( !attr.getID().equals("objectSid") )
								{
								}
								else
								{
									vals.nextElement();
								}
							}
						}
					}
				}
			}
			catch( PartialResultException pe ) {
			}
			catch(NamingException e) {
				e.printStackTrace();
				System.exit(0);
			}
	}
// ------------- デバック-------------------------------------------------------

}

