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

import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.mail.MailTX;
import org.opengion.fukurou.transfer.TransferConfig;
import org.opengion.fukurou.transfer.TransferProcess;
import org.opengion.fukurou.util.ApplicationInfo;
import org.opengion.fukurou.util.HybsTimerTask;
import org.opengion.fukurou.system.LogWriter;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.system.ThrowUtil;							// 6.4.2.0 (2016/01/29)
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import static org.opengion.fukurou.system.HybsConst.CR ;		// 6.1.0.0 (2014/12/26)
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * 【伝送システム】各読取方法、実行方法に応じて伝送処理を行うためのデーモンです。
 *
 * 読取、及び実行における具体的な処理については、{@link org.opengion.fukurou.transfer}パッケージ内の
 * 各実装クラスのドキュメントを参照して下さい。
 *
 * なお、各処理の実行について、トランザクションは、読取対象の単位になります。
 * 同じ読取対象で、異なる実行方法、実行対象を定義した場合、同じデータに対して複数回処理が行われます。
 * しかし、この場合においても、トランザクションは読取対象の単位で生成されるため、複数回の処理の内、
 * 1回でもエラーが発生した場合は、同じ読取対象でそれまでに処理した分についてもrollbackされます。
 *
 * 発生したエラーをメールで通知する場合は以下の設定を行う必要があります。
 * [システムリソース]
 *  COMMON_MAIL_SERVER
 *  ERROR_MAIL_FROM_USER
 * [伝送定義マスタ]
 *  エラー送信先
 * ※伝送定義マスタ読取時にエラーが発生した場合は、システムリソースの"ERROR_MAIL_TO_USERS"で
 *   設定さえたユーザーにメールが送信されます。
 *
 * ※処理中に何らかのエラーが1度でも発生した場合、このデーモンは停止します。
 *
 * このクラスは、HybsTimerTask を継承した タイマータスククラスです。
 * startDaemon() がタイマータスクによって、呼び出されます。
 *
 * @og.rev 5.4.1.0 (2011/11/01) 伝送システム対応
 * @og.group デーモン
 *
 * @version  5.0
 * @author   Hiroki Nakamura
 * @since    JDK6.0,
 */
public class Daemon_Transfer extends HybsTimerTask {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.4.2.0 (2016/01/29)" ;

	// 伝送定義マスタ検索用SQL
	private static final String GE62_SELECT =
		"SELECT A.KBREAD,A.READOBJ,A.READPRM,A.KBEXEC,A.EXECDBID,A.EXECOBJ,A.EXECPRM,A.ERROR_SENDTO" +
		" FROM GE62 A" +
		" WHERE A.FGJ = '1'";

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

	// HTTP接続時のプロキシホスト
	private static final String HTTP_PROXY_HOST = HybsSystem.sys( "HTTP_PROXY_HOST" );

	// HTTP接続時のプロキシポート
	private static final int HTTP_PROXY_PORT = HybsSystem.sysInt( "HTTP_PROXY_PORT" );

	// 呼び出し元ホストコード
	private static final String HFROM = HybsSystem.sys( "TRANSFER_HOST_CODE" );

	// ループカウンタを24回に設定
	private static final int LOOP_COUNTER = 24;

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

	private boolean running		= true;
	private int loopCnt			;

	private String ge62Select	;
	private String dmnName		;

	private ApplicationInfo appInfo ;
	private boolean debug		;

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

	/**
	 * このタイマータスクによって初期化されるアクションです。
	 * パラメータを使用した初期化を行います。
	 *
	 */
	@Override
	public void initDaemon() {
		debug = StringUtil.nval( getValue( "DEBUG" ),debug );

		dmnName = getName();

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		buf.append( GE62_SELECT );

		// システムIDは必須指定
		final String systemId = getValue( "SYSTEM_ID" );
		if( StringUtil.isNull( systemId ) ) {
			final String errMsg = "システムID方法は必須指定です。" ;
			throw new HybsSystemException( errMsg );
		}
		else {
			buf.append( " AND A.SYSTEM_ID='" ).append( systemId ).append( '\'' );
		}

		// 読取方法は必須指定
		final String kbRead = getValue( "KBREAD" );
		if( StringUtil.isNull( kbRead ) ) {
			final String errMsg = "読取方法は必須指定です。" ;
			throw new HybsSystemException( errMsg );
		}
		else {
			buf.append( " AND A.KBREAD='" ).append( kbRead ).append( '\'' );
		}

		// デーモングループは必須指定
		final String dmnGroup = getValue( "DMN_GRP" );
		if( StringUtil.isNull( dmnGroup ) ) {
			final String errMsg = "デーモングループは必須指定です。" ;
			throw new HybsSystemException( errMsg );
		}
		else {
			buf.append( " AND A.DMN_GRP='" ).append( dmnGroup ).append( '\'' );
		}

		buf.append( " ORDER BY A.READOBJ,A.KBEXEC,A.EXECOBJ" );

		ge62Select = buf.toString() ;

		if( debug ) {
			System.out.println( "DMN_NAME=[" + dmnName + "]" );
			System.out.println( "QUERY=[" + ge62Select + "]" );
		}

		if( USE_DB_APPLICATION_INFO ) {
			appInfo = new ApplicationInfo();
			// ユーザーID,IPアドレス,ホスト名
			appInfo.setClientInfo( systemId,HybsSystem.HOST_ADRS,HybsSystem.HOST_NAME );
			// 画面ID,操作,プログラムID
			appInfo.setModuleInfo( "TransferDaemon",dmnName,dmnName );
		}
		else {
			appInfo = null;
		}
	}

	/**
	 * タイマータスクのデーモン処理の開始ポイントです。
	 *
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#stringStackTrace(Throwable) を、ThrowUtil#ogStackTrace(String,Throwable) に置き換え。
	 */
	@Override
	protected void startDaemon() {
		if( loopCnt % LOOP_COUNTER == 0 ) {
			loopCnt = 1;
			System.out.println();
			System.out.print( toString() + " " + new Date()  + " " );
		}
		else {
			System.out.print( "." );
			loopCnt++ ;
		}

		// 伝送DB読取
		final GE62Data ge62Data = new GE62Data();
		try {
			// 6.0.2.5 (2014/10/31) refactoring:無効な代入です。
			final String[][] vals = DBUtil.dbExecute( ge62Select,null,appInfo,DBID );		// 5.5.5.1 (2012/08/07)
			if( vals != null && vals.length > 0 ) {
				for( int row=0; running && row<vals.length; row++ ) {
					ge62Data.addData( vals[row] );
				}
			}
		}
		catch( Throwable ex ) {
			final String header = "伝送読取エラー：DMN_NAME=[" + dmnName + "] , DMN_HOST=[" + HybsSystem.HOST_NAME + "] , QUERY=[" + ge62Select + "]";
//			final String errMsg = header + CR + StringUtil.stringStackTrace( ex ) ;
			final String errMsg = header + CR + ThrowUtil.ogStackTrace( ex ) ;				// 6.4.2.0 (2016/01/29)
			System.out.println( errMsg );
			LogWriter.log( errMsg );
			final String errorSendto = HybsSystem.sys( "ERROR_MAIL_TO_USERS" );
			sendMail( header, errMsg, errorSendto );
		}

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

		// 処理実行
		// 読取対象の単位で処理を実行します。(トランザクションもこの単位になります)
		for( final String tranKey : ge62Data.getTranSet() ) {
			TransferProcess proc = null;
			try {
				proc = new TransferProcess( ge62Data.getConfSet( tranKey ) );
				proc.setDmnName( dmnName );
				proc.setAppInfo( appInfo );
				if( debug ) {
					proc.setDebug();
				}

				proc.process();
			}
			catch( Throwable ex ) {
				// エラーが発生した場合はデーモンを停止します。
				cancel();

				// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid instantiating new objects inside loops
				// 最後に処理した伝送設定オブジェクトを下にエラーを出力します。
//				final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
//					.append( "伝送エラー：DMN_NAME=[" ).append( dmnName ).append( "] , DMN_HOST=[" )
//					.append( HybsSystem.HOST_NAME ).append( ']' );

				errMsg.setLength(0);				// new StringBuilder の代わり。
				errMsg.append( "伝送エラー：DMN_NAME=[" ).append( dmnName ).append( "] , DMN_HOST=[" ).append( HybsSystem.HOST_NAME ).append( ']' );

				String errorSendto = null;
				if( proc != null ) {				// 5.5.2.6 (2012/05/25) findbugs対応
					final TransferConfig config = proc.getLastConfig();
					if( config != null ) {
//						errMsg.append( " , CONFIG=[" ).append( config.toString() ).append( ']' );
						errMsg.append( " , CONFIG=[" ).append( config ).append( ']' );
						errorSendto = config.getErrorSendto();
					}
				}
				final String header = errMsg.toString();
//				final String body   = errMsg.append( CR ).append( StringUtil.stringStackTrace( ex ) ).toString() ;
				final String body   = errMsg.append( CR ).append( ThrowUtil.ogStackTrace( ex ) ).toString() ;				// 6.4.2.0 (2016/01/29)

//				errMsg.append( CR  ).append(  StringUtil.stringStackTrace( ex ) ) ;
//				System.out.println( errMsg );
//				LogWriter.log( errMsg.toString() );
//				sendMail( header, errMsg.toString(), errorSendto );

				System.out.println( body );
				LogWriter.log( body );
				sendMail( header, body, errorSendto );
			}
		}
	}

	/**
	 * このタイマータスクのcancel() メソッドをオーバーライドします。
	 * HybsTimerTaskManager#cancelTask( int ) を実行します。
	 *
	 * @return	スケジュールされている 1 回以上実行されない場合に true
	 * @see java.util.TimerTask#cancel()
	 */
	@Override
	public boolean cancel() {
		running = false;
		return super.cancel();
	}

	/**
	 * エラー情報のメール送信を行います。
	 * エラーメールは、システムパラメータ の COMMON_MAIL_SERVER(メールサーバー)と
	 * ERROR_MAIL_FROM_USER(エラーメール発信元)と、ERROR_MAIL_TO_USERS(エラーメール受信者)
	 * がすべて設定されている場合に、送信されます。
	 *
	 * @param	inHeader	ヘッダーメッセージ
	 * @param	inErrMsg	エラーメッセージ
	 * @param	errorSendto	エラー送信先
	 */
	protected void sendMail( final String inHeader, final String inErrMsg, final String errorSendto ) {

		final String   host = HybsSystem.sys( "COMMON_MAIL_SERVER" );
		final String   from = HybsSystem.sys( "ERROR_MAIL_FROM_USER" );
		final String[] to = StringUtil.csv2Array( errorSendto );

		if( host != null && from != null && to.length > 0 ) {
			try {
				final MailTX tx = new MailTX( host );
				tx.setFrom( from );
				tx.setTo( to );
				tx.setSubject( inHeader );
				tx.setMessage( inErrMsg );
				tx.sendmail();
			}
			catch( Throwable ex ) {
				final String errMsg = "エラー時メール送信に失敗しました。" + CR
							+ " SUBJECT:" + inHeader				+ CR
							+ " HOST:" + host						+ CR
							+ " FROM:" + from						+ CR
							+ " TO:"   + Arrays.toString( to )		+ CR
							+ ex.getMessage();
				LogWriter.log( errMsg );
				LogWriter.log( ex );
			}
		}
	}

	/**
	 * 伝送定義マスタから読み出したデータを管理します。
	 */
	private static class GE62Data {

		// トランザクションを生成するキーのセット(読取対象単位)
		private final Set<String> tranSet = new LinkedHashSet<>();
		// トランザクションキー(読取対象)に対する、設定オブジェクトのセット
		private final Map<String,Set<TransferConfig>> tran2ConfSet = new LinkedHashMap<>();

		/**
		 * GE62読取データを追加します。
		 *
		 * @param data GE62読取データ配列(可変長引数)
		 */
		private void addData( final String... data ) {
			final String kbRead		= data[0];
			final String readObj	= data[1];
			final String readPrm	= data[2];
			final String kbExec		= data[3];
			final String execDbid	= data[4];
			final String execObj	= data[5];
			final String execPrm	= data[6];
			final String errorSendto= data[7];

			final String tranKey = readObj;
			tranSet.add( tranKey );

			Set<TransferConfig> confSet = tran2ConfSet.get( tranKey );
			if( confSet == null ) {
				confSet = new LinkedHashSet<>();
			}
			final TransferConfig conf = new TransferConfig(
										kbRead, readObj, readPrm
										, kbExec, execDbid, execObj, execPrm
										, errorSendto, HFROM, HTTP_PROXY_HOST, HTTP_PROXY_PORT );
			confSet.add( conf );
			tran2ConfSet.put( tranKey, confSet );
		}

		/**
		 * トランザクション生成キー(読取対象)のセットを返します。
		 *
		 * @return トランザクション生成キー(読取対象)のセット
		 */
		private Set<String> getTranSet() {
			return tranSet;
		}

		/**
		 * トランザクション生成キー(読取対象)に対する設定オブジェクトのセットを返します。
		 *
		 * @param tranKey トランザクション生成キー(読取対象)
		 * @return 設定オブジェクトのセット
		 */
		private Set<TransferConfig> getConfSet( final String tranKey ) {
			return tran2ConfSet.get( tranKey );
		}
	}
}
