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

import org.opengion.fukurou.system.OgRuntimeException ;				// 6.4.2.0 (2016/01/29)
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.system.HybsConst.CR ;			// 6.1.0.0 (2014/12/26)
import static org.opengion.fukurou.util.StringUtil.csv2ArrayOnly;
import org.opengion.fukurou.db.DBUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.fukurou.security.HybsCryptography;				// 5.10.11.1 (2019/05/10)
import org.opengion.fukurou.util.StringUtil;						// 5.10.11.1 (2019/05/10)
import org.opengion.fukurou.util.XHTMLTag;							// 5.10.11.1 (2019/05/10)

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentSkipListMap;					// 6.4.3.1 (2016/02/12) refactoring

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

/**
 * メール定型文及びそれも基づいて各項目の合成を行うクラスです。
 * コンストラクタには定型文ID及びシステムIDで定型文マスタよりメールの定型文を取得します。
 * メール各項目のゲッターでは、定型文の内容を元にパラメータ値とマージして各項目を合成します。
 * 宛先について、セットした社員ID、グループIDと定型文の宛先設定に基づき、社員マスタと
 * グループマスタよりメールアドレス情報を取得して宛先マップを作成します。
 * 
 * 
 * @og.rev 5.6.6.0 (2013/07/05) host指定対応。GE37必須です。
 * 
 * @og.group メールモジュール
 *
 * @version  4.0
 * @author   Sen.Li
 * @since    JDK1.6
 */
public class MailPattern {

//	// 5.2.0.0 (2010/09/01) Ver4互換モード対応
//	// 6.9.5.0 (2018/04/23) VER4_COMPATIBLE_MODE 廃止
//	private static final String CONTENTS = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "CONTENT" : "CONTENTS";
//	private static final String ADDRESS  = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "MEMBER"  : "ADDRESS";
//	private static final String NAME_JA  = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "NAME"    : "NAME_JA";
//	private static final String KBNAME   = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "NM_KBN"  : "KBNAME";

	// 5.1.0.0 (2009/11/04) CONTENT ⇒ CONTENTS
	// 5.2.0.0 (2010/09/01) Ver4互換モード対応
	// 6.4.1.1 (2016/01/16) selGE31 → SEL_GE31 , selGE33 → SEL_GE33 , selGE37 → SEL_GE37 refactoring
	// 6.9.5.0 (2018/04/23) VER4_COMPATIBLE_MODE 廃止
//	private static final String SEL_GE31 	= "SELECT PTN_ID,FROM_ID,TO_ID,CC_ID,BCC_ID,TITLE,"+CONTENTS
	private static final String SEL_GE31 	= "SELECT PTN_ID,FROM_ID,TO_ID,CC_ID,BCC_ID,TITLE,CONTENTS"
											+ " ,JOKEN" // 5.6.6.0 (2013/07/05)
											+ " FROM GE31"
											+ " WHERE SYSTEM_ID =? AND PTN_ID=? AND FGJ='1'";
	// 5.1.0.0 (2009/11/04) MEMBER ⇒ ADDRESS , NM_KBN ⇒ KBNAME , NAME ⇒ NAME_JA
	// 5.2.0.0 (2010/09/01) Ver4互換モード対応
	// 6.9.5.0 (2018/04/23) VER4_COMPATIBLE_MODE 廃止
//	private static final String	SEL_GE33	= "SELECT A."+ADDRESS+",A."+NAME_JA+",B."+NAME_JA
	private static final String	SEL_GE33	= "SELECT A.ADDRESS,A.NAME_JA,B.NAME_JA"
											+ " FROM GE33 A,GE33 B"
											+ " WHERE A.FGJ='1' AND B.FGJ(+)='1' AND A.GROUP_ID=B.GROUP_ID(+)"
//											+ " AND A."+KBNAME+"='1' AND B."+KBNAME+"(+)='0' AND A.SYSTEM_ID=B.SYSTEM_ID(+)"
											+ " AND A.KBNAME='1' AND B.KBNAME(+)='0' AND A.SYSTEM_ID=B.SYSTEM_ID(+)"
											+ " AND A.SYSTEM_ID =? "
											+ " AND A.GROUP_ID =?";

	/**
	 * GE35のメールアドレス検索文
	 *
	 * @og.rev 6.4.1.1 (2016/01/16) selGE35 → SEL_GE35 refactoring
	 */
	// 6.9.5.0 (2018/04/23) VER4_COMPATIBLE_MODE 廃止
//	public static final String	SEL_GE35	= "SELECT  "+NAME_JA+",MAIL"
	public static final String	SEL_GE35	= "SELECT NAME_JA,MAIL"
											+ " FROM GE35"
											+ " WHERE USERID=?";

	// 5.6.6.0 (2013/07/05) 振り分け条件
	private static final String	SEL_GE37	= "SELECT  HOST,PORT,AUTH,AUTHUSER,AUTHPASS"
											+ " FROM GE37"
											+ " WHERE SYSTEM_ID = ?"
											+ " AND JOKEN = ?"
											+ " AND FGJ ='1'";

	// 内部データのカラム番号(定型文マスタテーブル)
	// 5.1.9.0 (2010/09/01) public ⇒ private へ変更
 //	private static final int GE31_PTN_ID	= 0 ;		// 未使用
	private static final int GE31_FROM_ID	= 1 ;
	private static final int GE31_TO_ID		= 2 ;
	private static final int GE31_CC_ID		= 3 ;
	private static final int GE31_BCC_ID	= 4 ;
	private static final int GE31_TITLE		= 5 ;
	private static final int GE31_CONTENTS	= 6 ;		// 5.1.0.0 (2009/11/04) CONTENT ⇒ CONTENTS
	private static final int GE31_JOKEN		= 7 ;		// 5.6.6.0 (2013/07/05)

	// 内部データのカラム番号(グループマスタ)
	private static final int GE33_ADDRESS 	= 0 ;		// 5.1.0.0 (2009/11/04) MEMBER ⇒ ADDRESS
	private static final int GE33_MNAME	 	= 1 ;
	private static final int GE33_GNAME	 	= 2 ;

	/**
	 * GE35のユーザ名称
	 */
	public static final int GE35_NAME	 	= 0 ;
	/**
	 * GE35のメールアドレス
	 */
	public static final int GE35_MAIL 		= 1 ;

	// 内部データのカラム番号(送信ホストマスタ)
	private static final int GE37_HOST	 	= 0 ;
	private static final int GE37_PORT 		= 1 ;
	private static final int GE37_AUTH 		= 2 ;
	private static final int GE37_AUTHUSER	= 3 ;
	private static final int GE37_AUTHPASS	= 4 ;

	// 内部データのカラム番号(宛先テーブル)
	/** カラム番号(宛先テーブル) {@value} */
	public static final int IDX_DST_ID   	= 0 ;
	/** カラム番号(宛先テーブル) {@value} */
	public static final int IDX_GROUP_ID 	= 1 ;
	/** カラム番号(宛先テーブル) {@value} */
	public static final int IDX_GROUP_NAME	= 2 ;
	/** カラム番号(宛先テーブル) {@value} */
	public static final int IDX_DST_NAME 	= 3 ;
	/** カラム番号(宛先テーブル) {@value} */
	public static final int IDX_DST_ADDR 	= 4 ;
	/** カラム番号(宛先テーブル) {@value} */
	public static final int IDX_DST_KBN  	= 5 ;
	/** カラム番号(宛先テーブル) {@value} */
	public static final int IDX_FGJ 	  	= 6 ;

	/** メール送信区分 {@value} */
	public static final int KBN_TO			= 0 ;	// メール送信区分(TO)
	/** メール送信区分 {@value} */
	public static final int KBN_CC			= 1 ;	// メール送信区分(CC)
	/** メール送信区分 {@value} */
	public static final int KBN_BCC			= 2 ;	// メール送信区分(BCC)

	private static final String PREFIX_GRP = "GP.";							// 6.4.1.1 (2016/01/16) PreFixGroup → PREFIX_GRP refactoring

	private final List<String> errAddrList	= new ArrayList<>();
	/** 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。  */
	private final ConcurrentMap<String, String> paramMap ;					// 6.4.3.3 (2016/03/04)
	private String  fromId			;
	private final String 	toId	;
	private final String 	ccId	;
	private final String 	bccId	;
	private String[][] 	title		;
	private String[][] 	content 	;

	// 5.6.6.0 (2013/07/05)
	private String host 	= HybsSystem.sys( "COMMON_MAIL_SERVER" );
	private String smtpPort	= HybsSystem.sys( "SMTP_PORT" );
	private String authType	= HybsSystem.sys( "MAIL_SEND_AUTH" );			// 6.0.3.0 (2014/11/13) Ver6用キーワード変更
	private String authPort	= HybsSystem.sys( "MAIL_SEND_AUTH_PORT" );		// 5.8.1.1 (2014/11/14)
	private String authUser	= HybsSystem.sys( "MAIL_SEND_AUTH_USER" );
	private String authPass	= HybsSystem.sys( "MAIL_SEND_AUTH_PASSWORD" );
//	// 5.9.29.2 (2018/02/16) STARTTLS対応(ｷｰﾜｰﾄﾞをVer5 にあわせます)
//	private boolean useTLS	= HybsSystem.sysBool( "MAIL_SEND_USE_STARTTLS" ); // 5.9.29.2 (2018/02/16)

	private final String DBID = HybsSystem.sys( "RESOURCE_DBID" );			// 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応

	private static final String urlCheckCrypt = HybsSystem.sys( "URL_CHECK_CRYPT" ); // 5.10.11.1 (2019/05/10)

	/**
	 * メール定型文オブジェクトを作成するコンストラクタです。
	 * 定型文マスタより取得したデータを各フィルードにセットします。
	 *
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 5.6.6.0 (2013/07/05) 振り分け対応
	 * @og.rev 6.0.3.0 (2014/11/13) Ver6用キーワード変更
	 * @og.rev 6.4.1.1 (2016/01/16) DefaultMailManager.appInfo → DefaultMailManager.APP_INFO refactoring
	 * @og.rev 6.4.3.3 (2016/03/04) 戻すMapが、not null制限つきであることを示すため、ConcurrentMap に置き換えます。
	 *
	 * @param	params	パラメータのマップ
	 */
	public MailPattern( final ConcurrentMap<String, String> params ){
		paramMap = params;
		final String sysId = params.get( "SYSTEM_ID" );
		final String pid   = params.get( "PTN_ID" );
		final String[] selGE30Args = { sysId,pid };
		final String[][] ptn = DBUtil.dbExecute( SEL_GE31, selGE30Args, DefaultMailManager.APP_INFO, DBID );	// 6.4.1.1 (2016/01/16)
		if( ptn == null || ptn.length <=0 ) {
			final String errMsg = "定型文取得できません。システムID：" + sysId + "定型文ID：" + pid + CR
								+ " SQL=" + SEL_GE31 + CR ;
			throw new OgRuntimeException( errMsg );
		}
		fromId 	= ptn[0][GE31_FROM_ID];
		toId 	= ptn[0][GE31_TO_ID];
		ccId 	= ptn[0][GE31_CC_ID];
		bccId 	= ptn[0][GE31_BCC_ID];
		final String tit = ptn[0][GE31_TITLE];
		if( tit != null && tit.length() > 0 ) {
			title = splitParam( tit );
		}
		final String con = ptn[0][GE31_CONTENTS];			// 5.1.0.0 (2009/11/04) CONTENT ⇒ CONTENTS
		if( con != null && con.length() > 0 ) {
			content = splitParam( con );
		}
		// 5.6.6.0 (2013/07/05) 振り分け対応 
		final String joken = ptn[0][GE31_JOKEN];
		if( joken != null && joken.length() > 0 ) {
			final String[] selGE37Args = { sysId,joken };
			final String[][] jkn = DBUtil.dbExecute( SEL_GE37, selGE37Args, DefaultMailManager.APP_INFO, DBID );	// 6.4.1.1 (2016/01/16)
			// 6.0.2.5 (2014/10/31) ptn → jkn の間違い訂正
			if( jkn == null || jkn.length <=0 ) {
				final String errMsg = "メールホストの振分先が取得できません。システムID：" + sysId + "振分条件：" + joken + CR
									+ " SQL=" + SEL_GE37 + CR ;
				throw new OgRuntimeException( errMsg );
			}
			host 		= jkn[0][GE37_HOST];
			smtpPort 	= jkn[0][GE37_PORT];
			authType	= jkn[0][GE37_AUTH];			// 6.0.3.0 (2014/11/13) Ver6用キーワード変更
			authUser	= jkn[0][GE37_AUTHUSER];
			authPass	= jkn[0][GE37_AUTHPASS];
		}
	}

	/**
	 * 定型文の送信者ID欄の設定値とパラメータ{&#064;FROM}を元に送信者アドレスを取得します。
	 * 定型文には{&#064;FROM}とセットされている場合は、パラメータ｛&#064;FROM｝の値を元に、社員IDをセット
	 * されている場合、直接そのIDを元に社員マスタから送信者アドレスを取得します。
	 * 送信者アドレス取得できなければ例外を投げます。
	 *
	 * @og.rev 4.3.7.5 (2009/07/08) 送信元名称が設定されていない場合は、アドレスを&lt;&gt;で囲わない
	 *
	 * @return	送信者アドレス
	 */
	public String getFromAddr(){
		// 定型文に{&#064;FROM}⇒ユーザーよりセットしたデータで置換える、ユーザーIDの場合はそのまま
		if( "{@FROM}".equals( fromId ) ) {
			fromId = paramMap.get( "FROM" );
		}
		paramMap.put( "FROM_ID"	, fromId );	// 送信者ID

		final String[] userInf = getUserInfo( fromId );
		String fromAddr = null;
		if( userInf != null && checkAddr ( fromId, userInf[1] ) ){ 	// 送信者メールアドレスチェック
			paramMap.put( "FROM_NAME", userInf[0] );				// 送信者名前
			// 4.3.7.5 (2009/07/08)
			if( userInf[0] != null && userInf[0].length() > 0 ) {
				fromAddr = userInf[0] + "<" + userInf[1] + ">" ;
			}
			else {
				fromAddr = userInf[1];
			}
			paramMap.put( "FROM_ADDR", fromAddr );					// 送信者メールアドレス
		}
		else {
			final String errMsg = "送信者ユーザー情報エラー。ユーザーID：" + fromId;
			throw new OgRuntimeException( errMsg );
		}

		return fromAddr;
	}

	/**
	 * マージ済のメールタイトルを返します。
	 *
	 * @return	メールタイトル
	 *
	 */
	public String getTitle(){
		return marge( title );
	}

	/**
	 * マージ済のメール本文を返します。
	 *
	 * @og.rev 5.1.0.0 (2009/11/04) HEADER ⇒ H_TXT , FOOTER ⇒ F_TXT カラム名変更
	 *
	 * @return	メール本文
	 * @og.rtnNotNull
	 */
	public String getContent(){
		final StringBuilder contentBuf = new StringBuilder( BUFFER_MIDDLE );
		final String header = paramMap.get( "HEADER" );		// 5.1.0.0 (2009/11/04) HEADER ⇒ H_TXT
		if( header != null ) {
			contentBuf.append( header ).append( '\n' );
		}
		contentBuf.append( marge( content ) ).append( '\n' );
		final String fooder = paramMap.get( "FOOTER" );		// 5.1.0.0 (2009/11/04) FOOTER ⇒ F_TXT
		if( fooder != null ) {
			contentBuf.append( fooder );
		}

		return contentBuf.toString();
	}

	/**
	 * 引数の文字列により、定数文字列の部分とパラメータの部分を分解します。
	 * 例："A{&#064;PARAM1}B"⇒rtn[0][0]="A",rtn[0][1]="B",rtn[1][0]="PARAM1"
	 *
	 * @param	src		分解対象の文字列
	 *
	 * @return	定数文字列の部分とパラメータの部分を分解した配列
	 * @og.rtnNotNull
	 */
	private String[][] splitParam( final String src ) {
		if( src == null ) { return new String[2][0]; }

		final ArrayList<String> listCons = new ArrayList<>() ;
		final ArrayList<String> listPara = new ArrayList<>() ;

		int start = 0;
		int index = src.indexOf( "{@" );
		while( index >= 0 ) {
			listCons.add( src.substring( start, index ) );
			final int end = src.indexOf( '}',index );
			if( end < 0 ) {
				final String errMsg = "{@ と } との対応関係がずれています。"
							+ "src=[" + src + "] : index=" + index ;
				throw new OgRuntimeException( errMsg );
			}
			listPara.add( src.substring( index + 2, end ));

			start = end+1 ;
			index = src.indexOf( "{@",start );
		}
		listCons.add ( src.substring( start ) );

		String[][] rtn = new String[2][];
		rtn[0] = listCons.toArray( new String[listCons.size()] );
		rtn[1] = listPara.toArray( new String[listPara.size()] );

		return rtn;
	}

	/**
	 * 送信先のアドレスをセットします。
	 * 定型文に定義されている宛先(TO、CC、BCC)を引数として渡します。引数には{&#064;TO}、{&#064;CC}、{&#064;BCC}が含まれています。
	 * {&#064;TO}、{&#064;CC}、{&#064;BCC}を初期設定の値で置換えて、実のメールアドレスマップを作成します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 毎回作成しているので、ローカルに移動する。
	 * @og.rev 6.4.3.3 (2016/03/04) 戻すMapが、not null制限つきであることを示すため、ConcurrentMap に置き換えます。
	 *
	 * @return 宛先のマップ
	 */
	public ConcurrentMap<String, String[]> getDstMap(){
		final String[] toBuf  = getDstArray( toId );
		final String[] ccBuf  = getDstArray( ccId );
		final String[] bccBuf = getDstArray( bccId );

		final ConcurrentMap<String, String[]> mailDstMap = new ConcurrentSkipListMap<>() ;

		// 送信先(TO、CC、BCC)のマップを作成します。
		setDstAddrMap( mailDstMap , bccBuf, KBN_BCC );
		setDstAddrMap( mailDstMap , ccBuf , KBN_CC  );
		setDstAddrMap( mailDstMap , toBuf , KBN_TO  );

		setDstWord( mailDstMap );

		return mailDstMap;
	}

	/**
	 * アドレスエラーのメッセージリストを返します。
	 *
	 * @return	メッセージリスト
	 */
	public List<String> getErrList(){
		return errAddrList;
	}

	/**
	 * 送信先のアドレスをcsv形式から配列にセットします。
	 * 引数は定型文マスタにセットしたものです。例：{&#064;TO},C12345,GP.IT
	 * ｛&#064;TO｝、｛&#064;CC｝、｛&#064;BCC｝はパラメータテーブルにセットしたもので置き換えます。
	 * 分解後の配列には、ユーザーID及びグループIDが混在します。
	 *
	 * @param	csvId	csv形式のアドレス
	 *
	 * @return	送信先のアドレスの配列
	 * @og.rtnNotNull
	 */
	private String[] getDstArray( final String csvId ){
		final String[] csvArr = csv2ArrayOnly( csvId );
		final List<String> list = new ArrayList<>();

		final int size = csvArr.length;
		for( int i=0; i<size; i++ ){
			if( csvArr[i].startsWith( "{@" )){
				final String[] tmp = csv2ArrayOnly( paramMap.get( csvArr[i].substring( 2, csvArr[i].length() - 1 ) ) );
				// 6.1.0.0 (2014/12/26) refactoring : Use asList instead of tight loops
				for( final String dst : tmp ) { list.add( dst ); }
			}
			else{
				list.add( csvArr[i] );
			}
		}
		return list.toArray( new String[list.size()] );
	}

	/**
	 * 送信先のアドレスマップを作成します。
	 *
	 *  ・引数 dstBuf にはユーザーIDとグループID混在する配列です。
	 *  ・ユーザーIDの場合、社員マスタのビューから名前及びメールアドレスを取得してマップにセットします。
	 *  ・グループIDの場合、グループマスタより、名前及びメールアドレスを取得してマップにセットします。
	 *  ・重複のユーザーが存在する場合、最後にセットした方が採用されます。
	 *
	 * @og.rev 5.1.0.0 (2009/11/04) MEMBER ⇒ ADDRESS カラム名変更
	 * @og.rev 6.4.3.1 (2016/02/12) dstMap をローカル変数にして、引数で渡すように変更。
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap を受け取ることを明確にするため、I/FをConcurrentMapに変更します。
	 *
	 * @param	dstMap	設定するMapオブジェクト
	 * @param	dstBuf	ユーザーIDとグループID混在する配列
	 * @param	kbn		送信区分[0:TO/1:CC/2:BCC]
	 */
	private void setDstAddrMap( final ConcurrentMap<String, String[]> dstMap, final String[] dstBuf, final int kbn ){
		 // IDX_DST_ID ,IDX_GROUP_ID, IDX_GROUP_NAME ,IDX_DST_NAME ,IDX_DST_ADDR ,IDX_DST_KBN ,IDX_FGJ
		 final String[] dstInit = { "", "", "", "", "", Integer.toString( kbn ), "7" };

		 final int len = dstBuf.length;
		 for( int i=0; i<len; i++ ){
			 if( dstBuf[i].startsWith( PREFIX_GRP ) ) { // グループIDの場合、グループマスタより、メンバーを取得します。
				final String[][] groupUsers = getGroupUsers( dstBuf[i].substring( PREFIX_GRP.length() ) );
				if( groupUsers.length > 0 ) {
					final int memberCnt = groupUsers.length;
					for( int j=0; j<memberCnt; j++ ) { // グループメンバーの処理
						String[] grpMember = dstInit.clone();
						grpMember[IDX_GROUP_ID]   = dstBuf[i].substring( PREFIX_GRP.length() ); // グループID
						grpMember[IDX_GROUP_NAME] = groupUsers[j][GE33_GNAME];    	// グループ名
						grpMember[IDX_DST_ID]     = groupUsers[j][GE33_ADDRESS];   	// 宛先ID
						grpMember[IDX_DST_NAME]   = groupUsers[j][GE33_MNAME];    	// 宛先名
						if( groupUsers[j][GE33_ADDRESS].contains( "@" ) ) {       	// メールアドレスがセットされる場合
							grpMember[IDX_DST_ADDR] = groupUsers[j][GE33_ADDRESS]; 	//　メールアドレス
						}
						else { // 社員IDがセットされた場合
							final String[] userAddr = getUserInfo( groupUsers[j][GE33_ADDRESS] );
							if( userAddr != null && userAddr.length > 0 ){
								grpMember[IDX_DST_ADDR] = userAddr[GE35_MAIL]; 	 	//　メールアドレス
							}
						}
						if( checkAddr( grpMember[IDX_DST_ID], grpMember[IDX_DST_ADDR] ) ){ // アドレス構文チェック
							grpMember[IDX_FGJ] = DefaultMailManager.FGJ_SEND_WAIT;      // 送信待
						}
						else {
							grpMember[IDX_FGJ] = DefaultMailManager.FGJ_ADDR_ERR;       // アドレス取得エラー
							errAddrList.add( "アドレス取得エラー。ユーザーID：" + grpMember[IDX_DST_ID] + " アドレス：" + grpMember[IDX_DST_ADDR] );
						}
						dstMap.put( groupUsers[j][GE33_ADDRESS], grpMember );
					}
				}
				else { // グループマスタよりメンバー取得できない場合
					String[] emptyGp = dstInit.clone();
					emptyGp[IDX_GROUP_ID] = dstBuf[i]; // グループID
					emptyGp[IDX_GROUP_NAME] = "*"; // グループID
					emptyGp[IDX_DST_ID] = "NO-MEMBER"; // 宛先ID
					dstMap.put( dstBuf[i], emptyGp );
				}
			}
			else { // ユーザーIDの場合
				String[] indMember = dstInit.clone();
				indMember[IDX_DST_ID] = dstBuf[i]; 							// 宛先ID
				indMember[IDX_GROUP_ID] = "*"; 								// グループID
				indMember[IDX_GROUP_NAME] = "*"; 							// グループID
				final String[] userAddr = getUserInfo( dstBuf[i] );
				if( userAddr != null && userAddr.length > 0 ){
					indMember[IDX_DST_NAME] = userAddr[GE35_NAME]; 			// 宛先名
					indMember[IDX_DST_ADDR] = userAddr[GE35_MAIL]; 			//　メールアドレス
					if( checkAddr( indMember[IDX_DST_ID], userAddr[1] ) ) {
						indMember[IDX_FGJ] = DefaultMailManager.FGJ_SEND_WAIT;
					}
					else {
						indMember[IDX_FGJ] = DefaultMailManager.FGJ_ADDR_ERR;
						errAddrList.add( "アドレス取得エラー。ユーザーID：" + indMember[IDX_DST_ID] + " アドレス：" + indMember[IDX_DST_ADDR] );
					}
				}
				else {
					indMember[IDX_FGJ] = DefaultMailManager.FGJ_ADDR_ERR; // 状況コード
				}
				dstMap.put( dstBuf[i], indMember );
			}
		}
	}

	/**
	 * グループマスタより、ユーザー情報を取得します。
	 * 戻り値の配列には、ユーザーIDまたはメールアドレス、ユーザー名、グループ名が格納されています。
	 *
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 6.4.1.1 (2016/01/16) DefaultMailManager.appInfo → DefaultMailManager.APP_INFO refactoring
	 *
	 * @param	groupId	グループID
	 *
	 * @return	ユーザー情報
	 */
	private String[][] getGroupUsers( final String groupId ){
		final String sysId = paramMap.get( "SYSTEM_ID" );
		final String[] ge33SelArgs = { sysId,groupId };
		final String[][] ge33Datas = DBUtil.dbExecute( SEL_GE33,ge33SelArgs,DefaultMailManager.APP_INFO, DBID );	// 6.4.1.1 (2016/01/16)

		if( ge33Datas.length == 0 ) {
			final String errMsg = "グループ情報取得できません。グループID：" + groupId ;
			if( "true".equals( paramMap.get( "ADDR_CHECK" ) ) ){
				throw new OgRuntimeException( errMsg );
			}
			else {
				errAddrList.add( errMsg );
			}
		}
		return ge33Datas;
	}

	/**
	 * メール送信ホストを返します。
	 * GE31のJOKEN(振り分け条件)を元に、GE37テーブルの HOST を取得します。
	 * 振り分け条件が未設定の場合は、システム定数のCOMMON_MAIL_SERVER を使用します。
	 *
	 * (初期値:システム定数のCOMMON_MAIL_SERVER[={@og.value SystemData#COMMON_MAIL_SERVER}])。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05)
	 *
	 * @return	メール送信ホスト
	 */
	public String getHost(){
		return host;
	}

	/**
	 * メール送信ポート番号を返します
	 * GE31のJOKEN(振り分け条件)を元に、GE37テーブルの PORT を取得します。
	 * 振り分け条件が未設定の場合は、システム定数のSMTP_PORT を使用します。
	 *
	 * (初期値:システム定数のSMTP_PORT[={@og.value SystemData#SMTP_PORT}])。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05)
	 *
	 * @return	メール送信ポート番号
	 */
	public String getSmtpPort(){
		return smtpPort;
	}

	/**
	 * メール送信時認証有無を返します
	 * GE31のJOKEN(振り分け条件)を元に、GE37テーブルの AUTH を取得します。
	 * 振り分け条件が未設定の場合は、システム定数のMAIL_SEND_AUTH を使用します。
	 *
	 * (初期値:システム定数のMAIL_SEND_AUTH[={@og.value SystemData#MAIL_SEND_AUTH}])。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) Ver6用キーワード変更
	 *
	 * @return	メール送信時認証有無
	 */
	public String getAuthType(){
		return authType;
	}

	/**
	 * メール送信認証ポートを返します。
	 * 「POP_BEFORE_SMTP」認証を行う場合に、POPサーバーに接続するポート番号を返します。
	 * GE37テーブルに指定するカラムはありません。
	 * 初期値は、システム定数のMAIL_SEND_AUTH_PORT を使用します。
	 *
	 * (初期値:システム定数のMAIL_SEND_AUTH_PORT[={@og.value SystemData#MAIL_SEND_AUTH_PORT}])。
	 *
	 * @og.rev 5.8.1.1 (2014/11/14) メール送信時認証「POP_BEFORE_SMTP」追加
	 *
	 * @return	メール送信認証ポート
	 */
	public String getAuthPort(){
		return authPort;
	}

	/**
	 * メール送信認証ユーザを返します
	 * GE31のJOKEN(振り分け条件)を元に、GE37テーブルの AUTHUSER を取得します。
	 * 振り分け条件が未設定の場合は、システム定数のMAIL_SEND_AUTH_USER を使用します。
	 *
	 * (初期値:システム定数のMAIL_SEND_AUTH_USER[={@og.value SystemData#MAIL_SEND_AUTH_USER}])。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05)
	 *
	 * @return	メール送信認証ユーザ
	 */
	public String getAuthUser(){
		return authUser;
	}

	/**
	 * メール送信認証パスワードを返します
	 * GE31のJOKEN(振り分け条件)を元に、GE37テーブルの AUTHPASS を取得します。
	 * 振り分け条件が未設定の場合は、システム定数のMAIL_SEND_AUTH_PASSWORD を使用します。
	 *
	 * (初期値:システム定数のMAIL_SEND_AUTH_PASSWORD[={@og.value SystemData#MAIL_SEND_AUTH_PASSWORD}])。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05)
	 *
	 * @return	メール送信認証パスワード
	 */
	public String getAuthPass(){
		return authPass;
	}

//	/**
//	 * メール送信時にSTARTTLSの暗号化を行うかを指定します。
//	 * 現在はシステム定数のMAIL_SEND_USE_STARTTLSをそのまま使用します。
//	 *
//	 * (初期値:システム定数のMAIL_SEND_AUTH_PASSWORD[={@og.value org.opengion.hayabusa.common.SystemData#MAIL_SEND_USE_STARTTLS}])。
//	 *
//	 * @og.rev 5.9.29.2 (2018/02/16) STARTTLS対応(ｷｰﾜｰﾄﾞをVer5 にあわせます)
//	 *
//	 * @return	メール送信時TLS利用有無
//	 */
//	public boolean getUseTLS(){
//		return useTLS;
//	}

	/**
	 * アドレスの構文チェックを行います、合法の場合は'true'(取得できた)、違法の場合は'false'(有効アドレス取得できず)を返します。
	 *
	 * @param   userId  ユーザーID
	 * @param   address アドレス
	 *
	 * @return  アドレスの構文チェック結果(true:取得/false:取得できず)
	 */
	private boolean checkAddr( final String userId, final String address ) {
		boolean rtn = true;
		try {
 			new InternetAddress( address );
		}
		catch( final AddressException aep ) {
			if( "true".equals( paramMap.get( "ADDR_CHECK" ) ) ) {
				final String errMsg = "ユーザーメールアドレスエラー。ユーザーID：" + userId + " アドレス：" + address;
				throw new OgRuntimeException( errMsg,aep );
			}
			rtn = false;
		}
		return rtn;
	}

	/**
	 * 引数の配列から文書を合成します。
	 * src[0]に配列には定数文字列、src[1]にはパラメータ
	 * 
	 * @og.rev 5.10.11.1 (2019/05/10) 予約語対応
	 *
	 * @param	src	引数の配列
	 *
	 * @return	合成された文章
	 * @og.rtnNotNull
	 */
	private String marge( final String[][] src ){
		final StringBuilder rtnBuf = new StringBuilder( BUFFER_MIDDLE );
		if( src != null ) {
			final int len = src[1].length;
			for( int i=0; i< len; i++ ) {
				rtnBuf.append( src[0][i] );
//				rtnBuf.append( paramMap.get( src[1][i] ) );
				rtnBuf.append( getReservedValue( src[1][i] ) ); // 5.10.11.1 (2019/05/10)
			}
			rtnBuf.append( src[0][len] );
		}
		return rtnBuf.toString();			// 6.1.0.0 (2014/12/26) refactoring
	}
	/**
	 * メールでのみ有効な予約語関係の処理を行います。
	 * 
	 * LINK.URLCはURLチェックを付加したURLを作成するための予約語です。
	 * {&#064;LINK.URLC url?key= &#064;PARAMx [CryptkeyStr]} ではをURL?key=ParamXにURLチェックを付加したものを返します。
	 * 第一パラメータはURLで、第二パラメータを付加します。複数のパラメータの付加は出来ません。
	 * ユーザ制限無し、期間は固定です。
	 *
	 * @og.rev 5.10.11.1 (2019/05/10) 新規作成
	 * 
	 * @param key 予約語
	 * @return 変換後文字列
	 */
	private String getReservedValue( final String key ) {
		if( key == null ) { return ""; }

		String rtn="" ;
		final int adrs = key.indexOf( '.' );
		if( adrs > 0 ) {
			final String subKey = key.substring( adrs+1 );
			
			// 利用頻度が低いと思われるため、この中で処理してしまう
			if( key.startsWith( "LINK.URLC" ) ) {
				final String[] vals = StringUtil.csv2Array( subKey,' ' );
				final String prm0 = vals[0] ; //URLCなので利用しない
				String prm1 = (vals.length >= 2) ? vals[1] : "" ; //URL
				String prm2 = (vals.length >= 3) ? vals[2] : "" ; //リクエスト変数
				String prm3 = (vals.length >= 4) ? vals[3] : urlCheckCrypt ;

				if( prm1 != null && prm1.startsWith( "@" ) ) {
					prm1 = paramMap.get( prm1.substring(1) );
				}
				if( prm2 != null && prm2.startsWith( "@" ) ) {
					prm2 = paramMap.get( prm2.substring(1) );
				}
				if( prm3 != null && prm3.startsWith( "@" ) ) {
					prm3 = paramMap.get( prm3.substring(1) );
				}
				
				rtn = prm1 + prm2; // URLとパラメータを合成
				
				if( rtn != null && rtn.length() > 0) {
					HybsCryptography criptCls = null;
					if( prm3 != null && prm3.length() > 0 ){
						criptCls = new HybsCryptography( prm3 );
					}
					//ユーザ指定なし、30日間有効のURLを作成
					rtn = XHTMLTag.addURLCheckKey( rtn, HybsSystem.URL_CHECK_KEY, "*", System.currentTimeMillis() + 1000L*60*60*24*30, criptCls );
				}
			}
		}
		else {
			rtn = paramMap.get(key);
		}
		return rtn;
	}

	/**
	 * 宛先(TO、CC、BCC)のID、名前、メールアドレスをパラメータマップにセットします。
	 *
	 * @param	mailDstMap	メールあて先マップ
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.entrySet() を使う様に変更。
	 * @og.rev 6.4.3.1 (2016/02/12) dstMap をローカル変数にして、引数で渡すように変更。
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap を受け取ることを明確にするため、I/FをConcurrentMapに変更します。
	 */
	private void setDstWord( final ConcurrentMap<String, String[]> mailDstMap ) {
		final StringBuilder to_id    = new StringBuilder( BUFFER_MIDDLE );
		final StringBuilder to_name  = new StringBuilder( BUFFER_MIDDLE );
		final StringBuilder to_addr  = new StringBuilder( BUFFER_MIDDLE );
		final StringBuilder cc_id    = new StringBuilder( BUFFER_MIDDLE );
		final StringBuilder cc_name  = new StringBuilder( BUFFER_MIDDLE );
		final StringBuilder cc_addr  = new StringBuilder( BUFFER_MIDDLE );
		final StringBuilder bcc_id   = new StringBuilder( BUFFER_MIDDLE );
		final StringBuilder bcc_name = new StringBuilder( BUFFER_MIDDLE );
		final StringBuilder bcc_addr = new StringBuilder( BUFFER_MIDDLE );
		int kbn;
		// 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.entrySet() を使う様に変更。
		for( final Map.Entry<String,String[]> entry : mailDstMap.entrySet() ) {
			final String   dstId   = entry.getKey();
			final String[] dstInfo = entry.getValue();

			kbn = Integer.parseInt( dstInfo[IDX_DST_KBN]);
			switch( kbn ) {
				case KBN_TO:
					to_id.append(   ',' ).append( dstId );
					to_name.append( ',' ).append( dstInfo[IDX_DST_NAME] );
					to_addr.append( ',' ).append( dstInfo[IDX_DST_NAME] )
						.append( '<' ).append( dstInfo[IDX_DST_ADDR] ).append( '>' );		// 6.0.2.5 (2014/10/31) char を append する。
					break;
				case KBN_CC:
					cc_id.append(   ',' ).append( dstId );
					cc_name.append( ',' ).append( dstInfo[IDX_DST_NAME] );
					cc_addr.append( ',' ).append( dstInfo[IDX_DST_NAME] )
						.append( '<' ).append( dstInfo[IDX_DST_ADDR] ).append( '>' );		// 6.0.2.5 (2014/10/31) char を append する。
					break;
				case KBN_BCC:
					bcc_id.append(   ',' ).append( dstId );
					bcc_name.append( ',' ).append( dstInfo[IDX_DST_NAME] );
					bcc_addr.append( ',' ).append( dstInfo[IDX_DST_NAME] )
						.append( '<' ).append( dstInfo[IDX_DST_ADDR] ).append( '>' );		// 6.0.2.5 (2014/10/31) char を append する。
					break;
				default:
					final String errMsg = "このアドレス区分がサポートされません。区分：" + kbn;
					throw new OgRuntimeException( errMsg );
			}
		}

		// 6.4.3.1 (2016/02/12) String#toString().substring( 1 ) でなく、StringBuilder#substring( 1 ) で、String が返されます。
		// 予約語マップに追加します。
		paramMap.put( "TO_ID"   , to_id		.length() > 0 ? to_id	.substring( 1 ) : "" );
		paramMap.put( "TO_NAME" , to_name	.length() > 0 ? to_name	.substring( 1 ) : "" );
		paramMap.put( "TO_ADDR" , to_addr	.length() > 0 ? to_addr	.substring( 1 ) : "" );
		paramMap.put( "CC_ID"   , cc_id		.length() > 0 ? cc_id	.substring( 1 ) : "" );
		paramMap.put( "CC_NAME" , cc_name	.length() > 0 ? cc_name	.substring( 1 ) : "" );
		paramMap.put( "CC_ADDR" , cc_addr	.length() > 0 ? cc_addr	.substring( 1 ) : "" );
		paramMap.put( "BCC_ID"  , bcc_id	.length() > 0 ? bcc_id	.substring( 1 ) : "" );
		paramMap.put( "BCC_NAME", bcc_name	.length() > 0 ? bcc_name.substring( 1 ) : "" );
		paramMap.put( "BCC_ADDR", bcc_addr	.length() > 0 ? bcc_addr.substring( 1 ) : "" );
	}

	/**
	 * 社員マスタより名前、メールアドレスを取得します。
	 * 戻り値 rtn[0]:ユーザー名、 rtn[1]:ユーザーメールアドレス
	 *
	 * @og.rev 4.3.6.6 (2009/05/15) メールアドレスが直接指定された場合に対応
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 6.4.1.1 (2016/01/16) DefaultMailManager.appInfo → DefaultMailManager.APP_INFO refactoring
	 *
	 * @param   userId  ユーザーID
	 *
	 * @return	配列文字列(rtn[0]:ユーザー名、 rtn[1]:ユーザーメールアドレス)
	 */
	private String[] getUserInfo( final String userId ){
		String[] rtn = null;

		if( userId.contains( "@" ) ) {
			rtn = new String[2];
			rtn[0] = "";
			rtn[1] = userId;
		}
		else {
			final String[] ge35SelArgs = { userId };
			final String[][] ge35Datas = DBUtil.dbExecute( SEL_GE35,ge35SelArgs,DefaultMailManager.APP_INFO, DBID );	// 6.4.1.1 (2016/01/16)
			if( ge35Datas.length > 0 ) {
				rtn = ge35Datas[0];
			}
			else {
				final String errMsg = "ユーザー情報取得できません。ユーザーID：" + userId ;
				if( "true".equals( paramMap.get( "ADDR_CHECK" ) ) ){
					throw new OgRuntimeException( errMsg );
				}
				else {
					errAddrList.add( errMsg );
				}
			}
		}
		return rtn;
	}
}
