/*
 * 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 org.opengion.fukurou.system.DateSet;							// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.util.StringUtil;						// 5.9.1.3 (2015/10/30)
import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.db.ApplicationInfo;
import org.opengion.fukurou.mail.MailTX;
import static org.opengion.fukurou.util.StringUtil.nval;
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

import org.opengion.hayabusa.common.HybsSystem;

import org.opengion.fukurou.db.ConnectionFactory;					// 5.9.26.0 (2017/11/02)
import org.opengion.fukurou.db.Transaction;							// 5.9.26.0 (2017/11/02)
import org.opengion.fukurou.db.TransactionReal;						// 5.9.26.0 (2017/11/02)
import org.opengion.fukurou.db.DBFunctionName;						// 5.9.26.0 (2017/11/02)

import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.stream.Collectors;									// 6.4.1.1 (2016/01/16)

import javax.mail.Address;
import javax.mail.SendFailedException;
import javax.mail.internet.InternetAddress;

/**
 * タグ mailSender2 及びバッチによる送信の共通処理部分を実装しています。
 * 送信タグ mailSender2 もしくは送信デーモンからパラメータを受取ります。
 * パラメータ中の定型文ID及びシステムIDで定型文マスタよりメールの定型文を取得して、
 * パラメータ値とマージしてメール文を合成します。同時に、宛先にセットした社員ID、
 * グループIDと定型文の宛先設定に基づき、社員マスタとグループマスタよりメールアドレス
 * 情報を取得して送信を行います。
 * エラーがなければ送信した内容を履歴テーブル、宛先テーブルに書き込みます。
 * 最後に本処理の呼出元に送信結果、エラーメッセージを返します。
 *
 * @og.group メールモジュール
 *
 * @version  4.0
 * @author   Sen.Li
 * @since    JDK1.6
 */
public class DefaultMailManager {
//	// 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";

	// 5.9.26.0 (2017/11/02) DB共通化のため、DBFunctionNameを使用して、シーケンスを取得するように変更

	// 5.0.3.0 (2009/11/04) CONTENT ⇒ CONTENTS
	// 5.2.0.0 (2010/09/01) Ver4互換モード対応
	// 6.4.1.1 (2016/01/16) insGE32 → INS_GE32 , insGE34 → INS_GE34 refactoring
	// 6.9.5.0 (2018/04/23) VER4_COMPATIBLE_MODE 廃止
//	private static final String	INS_GE32		= "INSERT INTO GE32(YKNO,PARA_KEY,PTN_ID,FROM_ADDR,TITLE,"+CONTENTS+",ATTACH1,ATTACH2,ATTACH3,ATTACH4,ATTACH5,DYSET,USRSET,PGUPD,SYSTEM_ID,FGJ)"
	private static final String	INS_GE32		= "INSERT INTO GE32(YKNO,PARA_KEY,PTN_ID,FROM_ADDR,TITLE,CONTENTS,ATTACH1,ATTACH2,ATTACH3,ATTACH4,ATTACH5,DYSET,USRSET,PGUPD,SYSTEM_ID,FGJ)"
											+ " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'1')";
	private static final String INS_GE34		= "INSERT INTO GE34(YKNO,DST_ID,GROUP_ID,DST_NAME,DST_ADDR,DST_KBN,FGJ,DYSET,USRSET,PGUPD)"
											+ " VALUES(?,?,?,?,?,?,?,?,?,?)";
	/** フラグ定数 {@value} */
	protected static final String FGJ_SEND_WAIT	= "0";
	/** フラグ定数 {@value} */
	protected static final String FGJ_SEND_OVER	= "1";
	/** フラグ定数 {@value} */
	protected static final String FGJ_ADDR_ERR	= "7";
	/** フラグ定数 {@value} */
	protected static final String FGJ_ACNT_ERR	= "8";

	// 内部データのカラム番号(履歴テーブル)
	private static final int GE32_YKNO		= 0 ;
	private static final int GE32_PARAKEY	= 1 ;
	private static final int GE32_PTN_ID 	= 2;
	private static final int GE32_FROM_ADDR	= 3;
	private static final int GE32_TITLE 	= 4;
	private static final int GE32_CONTENTS	= 5;		// 5.0.3.0 (2009/11/04) CONTENT ⇒ CONTENTS
	private static final int GE32_ATTACH1	= 6;
	private static final int GE32_ATTACH2	= 7;
	private static final int GE32_ATTACH3	= 8;
	private static final int GE32_ATTACH4	= 9;
	private static final int GE32_ATTACH5	= 10;
	private static final int GE32_DYSET		= 11;
	private static final int GE32_USRSET	= 12;
	private static final int GE32_PGUPD	= 13;
	private static final int GE32_SYSTEM_ID	= 14;
	// 内部データのカラム番号(履歴テーブル)
	private static final int GE34_YKNO	 	= 0 ;
	private static final int GE34_DST_ID 	= 1 ;
	private static final int GE34_GROUP_ID 	= 2 ;
	private static final int GE34_DST_NAME	= 3 ;
	private static final int GE34_DST_ADDR	= 4 ;
	private static final int GE34_DST_KBN	= 5 ;
	private static final int GE34_FGJ		= 6 ;
	private static final int GE34_DYSET		= 7 ;
	private static final int GE34_USRSET	= 8 ;
	private static final int GE34_PGUPD		= 9 ;
 //	private static String		host		= HybsSystem.sys( "COMMON_MAIL_SERVER" );
	private static String		charset		= HybsSystem.sys( "MAIL_DEFAULT_CHARSET" );
 //	private static String		smtpPort	= HybsSystem.sys( "SMTP_PORT" );				// 5.4.3.2 (2012/01/06)
 //	private static String		auth		= HybsSystem.sys( "MAIL_SEND_AUTH" );			// 5.4.3.2 (2012/01/06)
 //	private static String		authUser	= HybsSystem.sys( "MAIL_SEND_AUTH_USER" );		// 5.4.3.2 (2012/01/06)
 //	private static String		authPass	= HybsSystem.sys( "MAIL_SEND_AUTH_PASSWORD" );	// 5.4.3.2 (2012/01/06)
	private boolean	   			debugFlag	;
	private final List<String>	errAddrList = new ArrayList<>();
	private static final int MAX_RETRY 		= 3 ;	// メールアドレスエラー発生した場合、メール再送回数

	// 5.6.6.0 (2013/07/05) host等の外部指定に対応
	private String		host		= HybsSystem.sys( "COMMON_MAIL_SERVER" );		// 5.6.6.0 (2013/07/05)
	private String		smtpPort	= HybsSystem.sys( "SMTP_PORT" );				// 5.6.6.0 (2013/07/05)
	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" );		// 5.6.6.0 (2013/07/05)
	private String		authPass	= HybsSystem.sys( "MAIL_SEND_AUTH_PASSWORD" );	// 5.6.6.0 (2013/07/05)
	private boolean 	useTLS		= HybsSystem.sysBool( "MAIL_SEND_USE_STARTTLS" );	 // 5.9.29.2(2018/02/16) ｷｰﾜｰﾄﾞをVer5 にあわせます。

	private final boolean	useSSL		= HybsSystem.sysBool( "MAIL_SEND_USE_SSL" );	// 6.3.8.0 (2015/09/11)

	private String	mailTitle, mailContent, fromAddr;
	private String[] attachFiles;
	/** 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。  */
	private ConcurrentMap<String, String[]>	mailDstMap		;		// 6.4.3.3 (2016/03/04)
	/** 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。  */
	// 6.4.3.3 (2016/03/04) ConcurrentHashMap を受け取ることを明確にするため、I/FをConcurrentMapに変更します。
	private ConcurrentMap<String,String>	initParamMap	;		// パラメータマップ
	private MailTX							mail			;

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

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

	/** アプリケーション情報 */
	// 6.4.1.1 (2016/01/16) DefaultMailManager.appInfo → DefaultMailManager.APP_INFO refactoring
	protected static final ApplicationInfo APP_INFO;
	static {
		if( USE_DB_APPLICATION_INFO ) {
			APP_INFO = new ApplicationInfo();
			// ユーザーID,IPアドレス,ホスト名
			APP_INFO.setClientInfo( "MailModuel", HybsSystem.HOST_ADRS, HybsSystem.HOST_NAME );
			// 画面ID,操作,プログラムID
			APP_INFO.setModuleInfo( "MailModuel", "MailManager", "MailManager" );
		}
		else {
			APP_INFO = null;
		}
	}

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public DefaultMailManager() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * 呼出元よりパラメータマップを受取って、メールオブジェクト(mailObj)を作成します。
	 * メールオブジェクトは定型文ID及びシステムIDに基づいて定型文マスタからメールの定型文を取得して、
	 * パラメータ値とマージしてメール文の各項目を合成します。
	 * 宛先については、宛先にセットした社員ID、グループIDと定型文の宛先設定に基づき、社員マスタとグループマスタ
	 * よりメールアドレスを取得して宛先マップを作成します。
	 * まだ、添付ファイルのセット処理も行っています。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) host等の外部設定対応
	 * @og.rev 6.0.3.0 (2014/11/13) Ver6用キーワード変更
	 * @og.rev 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap を受け取ることを明確にするため、I/FをConcurrentMapに変更します。
	 * @og.rev 5.9.15.1 (2016/12/09) エラーリストのクリア
	 * @og.rev 5.9.29.2 (2018/02/16) STARTTLS対応(ｷｰﾜｰﾄﾞをVer5 にあわせます)
	 *
	 * @param	params	パラメータのマップ
	 */
	public void create( final ConcurrentMap<String, String> params ) {
		initParamMap = params;											// 6.4.3.1 (2016/02/12)
		final MailPattern mailObj = new MailPattern( params );
		fromAddr = mailObj.getFromAddr();
		setAttachFiles( params.get( "ATTACH1" )
					  , params.get( "ATTACH2" )
					  , params.get( "ATTACH3" )
					  , params.get( "ATTACH4" )
					  , params.get( "ATTACH5" ) );						// 添付ファイルのセット
		mailDstMap	= mailObj.getDstMap();								// 6.4.3.1 (2016/02/12)
		mailTitle	= mailObj.getTitle();
		mailContent = mailObj.getContent();
		errAddrList.clear();											// 5.9.15.1 (2016/12/09)
		errAddrList.addAll( mailObj.getErrList() );

		// 5.6.6.0 (2013/07/05)
		host 		= nval( mailObj.getHost() 		,host		);
		smtpPort 	= nval( mailObj.getSmtpPort() 	,smtpPort	);
		authType	= nval( mailObj.getAuthType()	,authType	);		// 6.0.3.0 (2014/11/13) Ver6用キーワード変更
		authPort	= nval( mailObj.getAuthPort()	,authPort	);		// 5.8.1.1 (2014/11/14)
		authUser	= nval( mailObj.getAuthUser() 	,authUser	);
		authPass	= nval( mailObj.getAuthPass() 	,authPass	);
//		useTLS		= mailObj.getUseTLS();			// 5.9.29.2 (2018/02/13)
		useTLS		= HybsSystem.sysBool( "MAIL_SEND_USE_STARTTLS" );	 // 5.9.29.2(2018/02/16) ｷｰﾜｰﾄﾞをVer5 にあわせます。
	}

	/**
	 * メール送信を行うメソッドです。
	 * createメソッドより合成した内容で送信を行います。アドレスエラー発生時に、
	 * ユーザー設定(addrCheck)により再送/中止します。
	 * メールサーバーに送信して、例外"SendFailedException"をキャッチできたら、
	 * メールアカウントがエラーとなるのを分かります。そして、例外のオブジェクトから
	 * エラーとなっているアカウントを取得して宛先マップから除外して、残りのアドレスに再送できます。
	 * 送信後履歴テーブル(GE32)、宛先テーブル(GE34)に送信結果を書き込みます。
	 *
	 * @og.rev 5.4.3.2 (2012/01/06) 送信時認証対応
	 * @og.rev 6.0.3.0 (2014/11/13) Ver6用キーワード変更
	 * @og.rev 6.3.8.0 (2015/09/11) SSL接続するかどうかを指定するパラメータを追加します。
	 * @og.rev 5.9.29.2 (2018/02/16) STARTTLS対応(ｷｰﾜｰﾄﾞをVer5 にあわせます)
	 */
	public void send() {
		final List<String> invalidAddrBuf	= new ArrayList<>();
		// mail = new MailTX( host, charset );
//		mail = new MailTX( host, charset, smtpPort, authType, authPort,authUser, authPass , useSSL );			// 6.3.8.0 (2015/09/11) SSL接続対応
		mail = new MailTX( host, charset, smtpPort, authType, authPort,authUser, authPass, useTLS , useSSL );	// 5.9.29.2 (2019/02/16)
		mail.setFrom( fromAddr );			// 送信者アドレス
		mail.setFilename( attachFiles ); 	// 添付ファイルをセットします。
		mail.setSubject( mailTitle );		// メールタイトル
		mail.setMessage( mailContent );		// メール本文
		mail.setDebug( debugFlag );
		setMailDst( invalidAddrBuf ); 		// 宛先をセットします。

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid instantiating new objects inside loops
		// 実際は、エラー処理の中で作成しているだけなので、外に出す必要性は、薄い。
		final StringBuilder errMsgBuf = new StringBuilder( BUFFER_MIDDLE );

		// メール送信を行います。
		int retryCount = MAX_RETRY;
		while( retryCount > 0 ) {
			try {
				mail.sendmail();
			}
			catch( final RuntimeException rex ) {
				final Throwable cause = rex.getCause();
				if( cause instanceof SendFailedException ) {
					final Address[] invAddress = ( (SendFailedException) cause ).getInvalidAddresses();
					if( invAddress != null ) {
						final int invCount = invAddress.length;
						for( int i=0; i<invCount; i++ ) {
							invalidAddrBuf.add( ( (InternetAddress) invAddress[i] ).getAddress() );
						}
					}
				}
				else {
					final String errMsg = "送信時にエラー発生しました。" + rex.getMessage();
					throw new OgRuntimeException( errMsg,rex );
				}
			}

			if( invalidAddrBuf.isEmpty() ) {
				retryCount = -1;
			}
			else {
				// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid instantiating new objects inside loops

				errMsgBuf.setLength(0);				// new StringBuilder の代わり。
				final String errMsg = errMsgBuf.append( "アドレスエラー。ユーザーID：" )
												.append( getUserIds( invalidAddrBuf ) )
												.append( " アドレス：" )
												.append( invalidAddrBuf.stream().collect( Collectors.joining( "," ) ) )
												.toString();

				if( "true".equals( initParamMap.get( "ADDR_CHECK" ) ) ){
					throw new OgRuntimeException( errMsg );
				}
				else {
					// メールアカウントチェックしない場合、無効のメールアドレスを除いて再送します。
					setMailDst( invalidAddrBuf );
					retryCount--;
					invalidAddrBuf.clear();
					errAddrList.add( errMsg );
				}
			}
		}
		commitMailDB();		// 送信結果を履歴テーブル、宛先テーブルにセットします。
	}

	/**
	 * デバッグ情報の表示を行うかどうか[true/false]をセットします。
	 *
	 * @param   debug  [true:出力する/それ以外:しない]
	 */
	public void setDebug( final boolean debug ) {
		debugFlag = debug;
	}

	/**
	 * メール送信者アドレスをセットします。
	 *
	 * @param  from 送信者アドレス
	 */
	public void setFromAddr( final String from ) {
		fromAddr = from;
	}

	/**
	 * メールタイトルをセットします。
	 *
	 * @param  title メールタイトル
	 */
	public void setTitle( final String title ) {
		mailTitle = title;
	}

	/**
	 * メール本文をセットします。
	 *
	 * @param  content メール本文
	 */
	public void setContent( final String content ) {
		mailContent = content;
	}

	/**
	 * メール送信ホストをセットします。
	 * 初期値は、システム定数のCOMMON_MAIL_SERVER を使用します。
	 *
	 * (初期値:システム定数のCOMMON_MAIL_SERVER[={@og.value SystemData#COMMON_MAIL_SERVER}])。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05)
	 *
	 * @param  hostName 送信ホスト
	 */
	public void setHost( final String hostName ) {
		host = nval( hostName, host );
	}

	/**
	 * メール送信ポート番号をセットします。
	 * 初期値は、システム定数のSMTP_PORT を使用します。
	 *
	 * (初期値:システム定数のSMTP_PORT[={@og.value SystemData#SMTP_PORT}])。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05)
	 *
	 * @param  port SMTPポート
	 */
	public void setPort( final String port ) {
		smtpPort = nval( port, smtpPort );
	}

	/**
	 * メール送信時認証有無をセットします。
	 * 認証を行う場合は「POP_BEFORE_SMTP」と指定して下さい。
	 * 認証時には認証ユーザと認証パスワードを設定する必要があります。
	 * 初期値は、システム定数のMAIL_SEND_AUTH を使用します。
	 *
	 * (初期値:システム定数のMAIL_SEND_AUTH[={@og.value SystemData#MAIL_SEND_AUTH}])。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) Ver6用キーワード変更
	 *
	 * @param  type 認証方式
	 */
	public void setAuthType( final String type ) {
		authType = nval( type, authType );
	}

	/**
	 * メール送信認証ユーザをセットします。
	 * 初期値は、システム定数のMAIL_SEND_AUTH_USER を使用します。
	 *
	 * (初期値:システム定数のMAIL_SEND_AUTH_USER[={@og.value SystemData#MAIL_SEND_AUTH_USER}])。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05)
	 *
	 * @param  user 認証ユーザ
	 */
	public void setAuthUser( final String user ) {
		authUser = nval( user, authUser );
	}

	/**
	 * メール送信認証パスワードをセットします。
	 * 初期値は、システム定数のMAIL_SEND_AUTH_PASSWORD を使用します。
	 *
	 * (初期値:システム定数のMAIL_SEND_AUTH_PASSWORD[={@og.value SystemData#MAIL_SEND_AUTH_PASSWORD}])。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05)
	 *
	 * @param  pass 認証パスワード
	 */
	public void setAuthPass( final String pass ) {
		authPass = nval( pass, 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 にあわせます)
	 *
	 * @param  flg TLSの利用有無
	 */
	public void setUseTLS( final boolean flg ) {
		useTLS = flg;
	}

	/**
	 * メール送信者アドレスを返します。
	 *
	 * @return	送信者アドレス
	 */
	public String getFromAddr() {
		return fromAddr;
	}

	/**
	 * メールタイトルを返します。
	 *
	 * @return	メールタイトル
	 */
	public String getTitle() {
		return mailTitle;
	}

	/**
	 * メール本文を返します。
	 *
	 * @return	メール本文
	 */
	public String getContent() {
		return mailContent;
	}

	/**
	 * 送信結果を履歴テーブル(GE32)と宛先テーブル(GE34)に登録します。
	 * 登録時に、桁数オーバーにならないように、テーブル定義の桁数を上限として、
	 * 登録前に各項目の桁数整理を行います。
	 *
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 5.9.1.3 (2015/10/30) 4000文字カットをやめる
	 * @og.rev 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.values() を使う様に変更。
	 * @og.rev 6.4.2.0 (2016/01/29) DateSet.getDate( String ) を利用するように修正します。
	 */
	protected void commitMailDB(){
		// 履歴テーブルの追加
		String[] insGE32Args = new String[15];
		final String ykno = getYkno();
		insGE32Args[GE32_YKNO]		= ykno;
		insGE32Args[GE32_PARAKEY] 	= initParamMap.get( "PARAKEY" );
		insGE32Args[GE32_PTN_ID] 	= trim( initParamMap.get( "PTN_ID" ), 20 );
		insGE32Args[GE32_FROM_ADDR] = trim( fromAddr, 100);
		insGE32Args[GE32_TITLE] 	= trim( mailTitle, 300);
		insGE32Args[GE32_CONTENTS] 	= mailContent;					// 5.9.1.3 (2015/10/30)
		insGE32Args[GE32_ATTACH1] 	= "";
		insGE32Args[GE32_ATTACH2] 	= "";
		insGE32Args[GE32_ATTACH3] 	= "";
		insGE32Args[GE32_ATTACH4] 	= "";
		insGE32Args[GE32_ATTACH5] 	= "";
		if( attachFiles != null ) {
			final int attSize = attachFiles.length;
			for( int i=0; i<attSize; i++ ) {
				insGE32Args[6 + i] = trim( attachFiles[i], 256);
			}
		}
		insGE32Args[GE32_DYSET]  = DateSet.getDate( "yyyyMMddHHmmss" );			// 6.4.2.0 (2016/01/29)
		insGE32Args[GE32_USRSET] = initParamMap.get( "LOGIN_USERID" );
		insGE32Args[GE32_PGUPD] = initParamMap.get( "PGID" );
		insGE32Args[GE32_SYSTEM_ID] = initParamMap.get( "SYSTEM_ID" );
		DBUtil.dbExecute( INS_GE32, insGE32Args, APP_INFO, DBID );	// 5.5.5.1 (2012/08/07)

		// 宛先テーブル追加
		String[] insGE34Args = new String[10];
		insGE34Args[GE34_YKNO]= ykno;

		// 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.values() を使う様に変更。
		for( final String[] dstInfo : mailDstMap.values() ) {
			insGE34Args[GE34_DST_ID] 	= trim( dstInfo[MailPattern.IDX_DST_ID]  , 10 );
			insGE34Args[GE34_GROUP_ID] 	= trim( dstInfo[MailPattern.IDX_GROUP_ID], 20 );
			insGE34Args[GE34_DST_NAME] 	= trim( dstInfo[MailPattern.IDX_DST_NAME], 20 );
			insGE34Args[GE34_DST_ADDR] 	= trim( dstInfo[MailPattern.IDX_DST_ADDR], 100 );
			insGE34Args[GE34_DST_KBN] 	= dstInfo[MailPattern.IDX_DST_KBN];
			insGE34Args[GE34_FGJ] 		= dstInfo[MailPattern.IDX_FGJ];
			insGE34Args[GE34_DYSET] 	= DateSet.getDate( "yyyyMMddHHmmss" );			// 6.4.2.0 (2016/01/29)
			insGE34Args[GE34_USRSET] 	= initParamMap.get( "LOGIN_USERID" );
			insGE34Args[GE34_PGUPD] 	= initParamMap.get( "PGID" );
			DBUtil.dbExecute( INS_GE34, insGE34Args, APP_INFO, DBID );		// 5.5.5.1 (2012/08/07)
		}
	}

	/**
	 * パラメータマップをセットします。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap を受け取ることを明確にするため、I/FをConcurrentMapに変更します。
	 *
	 * @param	params	パラメータのマップ
	 */
	protected void setInitParams( final ConcurrentMap<String, String> params ) {
		initParamMap = params;
	}

	/**
	 * 添付ファイル配列をセットします。
	 *
	 * @param	attach1	添付ファイル名1
	 * @param	attach2	添付ファイル名2
	 * @param	attach3	添付ファイル名3
	 * @param	attach4	添付ファイル名4
	 * @param	attach5	添付ファイル名5
	 */
	protected void setAttachFiles( final String attach1
								, final String attach2
								, final String attach3
								, final String attach4
								, final String attach5 ) {
		final List<String> fileList = new ArrayList<>();
		if( attach1 != null && attach1.length() != 0 ) { fileList.add( attach1 ); }
		if( attach2 != null && attach2.length() != 0 ) { fileList.add( attach2 ); }
		if( attach3 != null && attach3.length() != 0 ) { fileList.add( attach3 ); }
		if( attach4 != null && attach4.length() != 0 ) { fileList.add( attach4 ); }
		if( attach5 != null && attach5.length() != 0 ) { fileList.add( attach5 ); }
		attachFiles = fileList.toArray( new String[fileList.size()] );
	}

	/**
	 * メール宛先マップをセットします。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap を受け取ることを明確にするため、I/FをConcurrentMapに変更します。
	 *
	 * @param   mailDst	メール宛先マップ
	 */
	protected void setMailDstMap( final ConcurrentMap<String, String[]> mailDst ) {
		mailDstMap = mailDst;
	}

	/**
	 * メール宛先マップをセットします。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。
	 * @og.rev 6.4.3.1 (2016/02/12) ConcurrentMap 系は、key,val ともに not null 制限です。
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap を受け取ることを明確にするため、I/FをConcurrentMapに変更します。
	 *
	 * @return	メール宛先マップ
	 */
	protected ConcurrentMap<String, String[]> getMailDstMap() {
		return mailDstMap;
	}

	/**
	 * 指定の長さ以内の文字列を返します。
	 *
	 * @og.rev 5.9.1.3 (2015/10/30) 文字数ではなくByte数に変更
	 *
	 * @param	src		オリジナルの文字列
	 * @param	maxLen	指定の長さ
	 *
	 * @return	指定の長さに短縮された文字列
	 */
	protected String trim( final String src, final int maxLen ) {
		String rtn = src;
		if( src != null && src.length() > maxLen ) {
			rtn = StringUtil.cut( src, maxLen );		// 5.9.1.3 (2015/10/30)
		}
		return rtn;
	}

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

	/**
	 * 宛先マップを元に、送信オブジェクトに宛先をセットします。
	 * セットする際に、アカウントエラーとなっているアドレスを除外します。
	 * 宛先が存在しない場合、例外を投げます。
	 *
	 * @og.rev 4.3.7.5 (2009/07/08) 送信先名称が設定されていない場合は、アドレスを<>で囲わない
	 * @og.rev 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.values() を使う様に変更。
	 *
	 * @param invalidAddr 宛先のリスト
	 */
	private void setMailDst( final List<String> invalidAddr ){
		final ArrayList<String> toList = new ArrayList<>();
		final ArrayList<String> ccList = new ArrayList<>();
		final ArrayList<String> bccList = new ArrayList<>();

		// これ、今見たけど、こんな使い方はどうかと思う。
		final Map<Integer, ArrayList<String>> tempMap = new HashMap<>();
		tempMap.put( Integer.valueOf( MailPattern.KBN_TO ),  toList );
		tempMap.put( Integer.valueOf( MailPattern.KBN_CC ),  ccList );
		tempMap.put( Integer.valueOf( MailPattern.KBN_BCC ), bccList );

		// 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.values() を使う様に変更。
		for( final String[] dstInfo : mailDstMap.values() ) {
			final Integer kbn = Integer.valueOf( dstInfo[MailPattern.IDX_DST_KBN] );
			// 6.1.1.0 (2015/01/17) PMD Avoid if(x != y) ..; else ..;
			if( invalidAddr.contains( dstInfo[MailPattern.IDX_DST_ADDR] )
					|| FGJ_ADDR_ERR.equals( dstInfo[MailPattern.IDX_FGJ] )){
				if( FGJ_SEND_OVER.equals( dstInfo[MailPattern.IDX_FGJ] ) ) {
					dstInfo[MailPattern.IDX_FGJ] = FGJ_ACNT_ERR;
				}
			}
			else {
				dstInfo[MailPattern.IDX_FGJ] = FGJ_SEND_OVER;

				// 4.3.7.5 (2009/07/08)
				final String name = dstInfo[MailPattern.IDX_DST_NAME];
				if( name != null && name.length() > 0 ) {
					tempMap.get( kbn ).add( dstInfo[MailPattern.IDX_DST_NAME] +  "<"+ dstInfo[MailPattern.IDX_DST_ADDR] + ">" );
				}
				else {
					tempMap.get( kbn ).add( dstInfo[MailPattern.IDX_DST_ADDR] );
				}
			}
		}

		mail.clearTo();		// 宛先(TO)をクリア
		mail.clearCc();		// 宛先(CC)をクリア
		mail.clearBcc();	// 宛先(BCC)をクリア
		boolean haveValidAddr = false ;
		if( ! toList.isEmpty() ) { 	// toのセット
			haveValidAddr = true;
			final String[] to = toList.toArray( new String[toList.size()] );
			mail.setTo( to );
		}
		if( ! ccList.isEmpty() ) {	// ccのセット
			haveValidAddr = true;
			final String[] cc = ccList.toArray( new String[ccList.size()] );
			mail.setCc( cc );
		}
		if( ! bccList.isEmpty() ) {	// bccのセット
			haveValidAddr = true;
			final String[] bcc = bccList.toArray( new String[bccList.size()] );
			mail.setBcc( bcc );
		}
		if( !haveValidAddr ){		// 宛先全部無効の場合、例外を投げます。
			final String errMsg = "宛先のメールアドレスが有効ではありません。"
						+ "TO , CC , BCC のいづれにもアドレスが設定されていません。"; // 5.1.8.0 (2010/07/01) errMsg 修正
			throw new OgRuntimeException( errMsg );
		}
	}

	/**
	 * 要求NOを採番します。
	 * この要求NOで履歴テーブル(GE32)と宛先テーブル(GE30)の関連付けを持たせます。
	 *
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 5.9.26.0 (2017/11/02) DB共通化対応
	 * @og.rev 7.0.6.4 (2019/11/29) TransactionRealのclose漏れ対応
	 *
	 * @return	要求NO
	 */
	private String getYkno() {
//		// 5.9.26.0 (2017/11/02) DBFnctionNameを利用して、シーケンスを取得
		String selYkno = "";
//		final Transaction tran = new TransactionReal( APP_INFO );
//		try{
		try( Transaction tran = new TransactionReal( APP_INFO ) ) {				// 7.0.6.4 (2019/11/29) try-with-resources文
			selYkno = String.valueOf(DBFunctionName.getDBName(ConnectionFactory.getDBName(DBID)).getSequence("GE32S02", tran));
//		}finally{
//			tran.close();
		}
		return selYkno;
	}

	/**
	 * メールアドレスのリストよりユーザーIDを逆引きします。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) refactoring : メールアドレスのリスト件数を見てから、処理の実行を決めます。
	 * @og.rev 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.entrySet() を使う様に変更。
	 * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
	 *
	 * @param	addressList	メールアドレスのリスト(not null)
	 *
	 * @return	ユーザーID
	 * @og.rtnNotNull
	 */
	private String getUserIds( final List<String> addressList ){
		final int size = addressList.size() ;
		if( size == 0 ) { return "" ; }

		final Map<String,String> addressMap = new HashMap<>();
		// 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.entrySet() を使う様に変更。

		// 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
		mailDstMap.forEach( (uid,inf) -> addressMap.put( inf[MailPattern.IDX_DST_ADDR], uid ) );

		final StringBuilder idBuf = new StringBuilder( BUFFER_MIDDLE )
			.append( addressMap.get( addressList.get( 0 ) ) );		// 先に size == 0 判定は終わっている。
		for( int i=1; i<size; i++ ) {
			idBuf.append( ',' ).append( addressMap.get( addressList.get( i ) ) );
		}
		return idBuf.toString();
	}
}
