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

import org.opengion.fukurou.util.Argument;
import org.opengion.fukurou.util.FTPConnect;
import org.opengion.fukurou.system.LogWriter;

import java.util.Map ;
import java.util.LinkedHashMap ;

import java.io.File;

/**
 * Process_FileFtp は、上流から受け取った FileLineModel を処理する、
 * ChainProcess インターフェースの実装クラスです。
 *
 * 上流から受け取った FileLineModel の ファイルから、localPath のローカル共通パスを
 * remotePath のFTP共通パスに、PFT伝送します。(-command=PUT 処理のみ)
 * ファイルそのものの階層構造は、維持されるため、ローカルからFTPサーバー
 * へのフォルダコピーに近いイメージになります。
 *
 * Process_FileCopy との違いは、ファイルのエンコード変換は行いません。ただし、
 * FTP伝送での改行コードの変換は、-mode=ASCII で指定できます。
 * もうひとつ、Process_FileCopy では、inPath と outPath でのCOPY処理でしたが、
 * このクラスでは、localPath と、remotePath でそれぞれの共通パスを指定します。
 *
 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト
 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを
 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し
 * できれば、使用可能です。
 *
 * 引数文字列中に空白を含む場合は、ダブルコーテーション("") で括って下さい。
 * 引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に
 * 繋げてください。
 *
 * @og.formSample
 *  Process_FileFtp -host=FTPサーバー -user=ユーザー -pass=パスワード -localPath=ローカル共通パス -remotePath=FTP共通パス
 *                   [-mode=[ASCII/BINARY]  ] [-passive=[true/false] ]
 *
 *    -host=FTPサーバー             ：FTPサーバー
 *    -user=ユーザー                ：ユーザー
 *    -pass=パスワード              ：パスワード
 *    -localPath=ローカル共通パス   ：上流で検索されたファイルパスのローカル側共通部分
 *    -remotePath=FTP共通パス       ：上流で検索されたファイルパスのFTP側共通部分
 *   [-mode=[ASCII/BINARY]  ]       ：扱うファイルの種類を指定します(初期値:ASCII)
 *   [-passive=[true/false] ]       ：パッシブモード(ローカルからサーバーへ接続を張る)を利用するかどうか(初期値:true)
 *                                      (false:アクティブモード(通常のFTPの初期値)で通信します。)
 *   [-mkdirs=[true/false]  ]       ：受け側ファイル(GET時:LOCAL、PUT時:FTPサーバー)にディレクトリを作成するかどうか(初期値:true)
 *                                      (false:ディレクトリが無ければ、エラーにします。)
 *   [-encode=エンコード名 ]        ：日本語ファイル名などのエンコード名を指定します(初期値:UTF-8)
 *   [-timeout=タイムアウト[秒] ]   ：Dataタイムアウト(初期値:600 [秒])
 *   [-display=[false/true] ]       ：trueは、検索状況を表示します(初期値:false)
 *   [-debug=[false/true]   ]       ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
 *
 * @og.rev 5.1.5.0 (2010/04/01) 新規追加
 *
 * ※ 注意
 *    Windwosにおいて、大量ファイルのFTP伝送を行う場合は、注意が必要です。
 *    Windowsにおけるソケットの最大値は、5000がデフォルト値です。
 *    また、TIME_WAITのデフォルト値は、4分(=240秒)です。
 *    FTPの様にデータ伝送時に毎回、ソケットを作成すると、ポートが枯渇します。
 *
 *    この値を変更するには、レジストリに以下のキーを設定する必要があります。
 *
 *    ■ソケットの最大数(5,000～65,534の間で設定)：
 *    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort (DWORD)
 *
 *    ■TIME_WAITの時間(30～300秒の間で設定)：
 *    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay (DWORD)
 *
 *    ※ 設定後は再起動しないと設定が反映されません。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class Process_FileFtp extends AbstractProcess implements ChainProcess {
	private static final int     TIMEOUT = 600 ;	// Dataタイムアウト(初期値:600 [秒])

	private FTPConnect	ftp		;

	private String	host 		;			// FTPサーバー
	private String	user 		;			// ユーザー
	private static final String	command		= "PUT";	// FTPサーバー側での処理の方法(GET/PUT/DEL)を指定します。(PUTのみ固定)
	private String	localPath	;			// 上流で検索されたファイルパスのローカル側共通部分
	private String	remotePath	;			// 上流で検索されたファイルパスのFTP側共通部分

	private boolean	display		;			// trueは、検索状況を表示します(初期値:false)
	private boolean	debug		;			// デバッグ情報を標準出力に表示する(true)かしない(false)か

	private int	localPathLen	;
	private int	inCount			;

	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,String> MUST_PROPARTY   ;		// ［プロパティ］必須チェック用 Map
	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,String> USABLE_PROPARTY ;		// ［プロパティ］整合性チェック Map

	private static final String[] MODE_LST = { "ASCII","BINARY" };

	static {
		MUST_PROPARTY = new LinkedHashMap<>();
		MUST_PROPARTY.put( "host",		"接続先のFTPサーバーのアドレスまたは、サーバー名(必須)" );
		MUST_PROPARTY.put( "user",		"接続するユーザー名(必須)" );
		MUST_PROPARTY.put( "pass",		"接続するユーザーのパスワード(必須)" );
		MUST_PROPARTY.put( "localPath",	"上流で検索されたファイルパスのローカル側共通部分(必須)" );
		MUST_PROPARTY.put( "remotePath",	"上流で検索されたファイルパスのFTP側共通部分(必須)" );

		USABLE_PROPARTY = new LinkedHashMap<>();
		USABLE_PROPARTY.put( "mode",			"扱うファイルの種類(ASCII/BINARY)を指定します(初期値:ASCII)" );
	//	USABLE_PROPARTY.put( "command",		"FTPサーバー側での処理の方法(GET/PUT/DEL)を指定します(初期値:PUT)" );
		USABLE_PROPARTY.put( "passive",		"パッシブモード(ローカルからサーバーへ接続を張る)を利用するかどうか(初期値:true)" );
		USABLE_PROPARTY.put( "mkdirs",		"受け側ファイル(GET時:LOCAL、PUT時:FTPサーバー)にディレクトリを作成するかどうか(初期値:true)" );
		USABLE_PROPARTY.put( "encode",		"日本語ファイル名などのエンコード名を指定します(初期値:UTF-8)" );
		USABLE_PROPARTY.put( "timeout",		"Dataタイムアウト(初期値:600 [秒])" );
		USABLE_PROPARTY.put( "display",		"[false/true]:trueは、検索状況を表示します(初期値:false)" );
		USABLE_PROPARTY.put( "debug",		"デバッグ情報を標準出力に表示する(true)かしない(false)か" +
											CR + "(初期値:false:表示しない)" );
	}

	/**
	 * デフォルトコンストラクター。
	 * このクラスは、動的作成されます。デフォルトコンストラクターで、
	 * super クラスに対して、必要な初期化を行っておきます。
	 *
	 */
	public Process_FileFtp() {
		super( "org.opengion.fukurou.process.Process_FileFtp",MUST_PROPARTY,USABLE_PROPARTY );
	}

	/**
	 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
	 * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
	 *
	 * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
	 */
	public void init( final ParamProcess paramProcess ) {
		final Argument arg = getArgument();

		final String pass = arg.getProparty( "pass" );			// パスワード
		host = arg.getProparty( "host");			// FTPサーバー
		user = arg.getProparty( "user" );			// ユーザー

		ftp = new FTPConnect();

		ftp.setHostUserPass( host , user , pass );

		// localPath と、remotePath をセットします。
		localPath	= arg.getProparty("localPath" );
		remotePath	= arg.getProparty("remotePath" );

		localPathLen  = localPath.length();

		// FTP処理に必要な各種パラメータをセットします。
		ftp.setMode(	arg.getProparty( "mode"		,"ASCII",MODE_LST	));		// 扱うファイルの種類を指定します。
		ftp.setPassive(	arg.getProparty( "passive"	,true				));		// パッシブモードを利用するかどうか
		ftp.setMkdirs(	arg.getProparty( "mkdirs"	,true				));		// 受け側ファイルにディレクトリを作成するかどうか
		ftp.setEncode(	arg.getProparty( "encode"	,"UTF-8"			));		// 日本語ファイル名などのエンコード名を指定します(初期値:UTF-8)
		ftp.setTimeout(	arg.getProparty( "timeout"	,TIMEOUT			));		// Dataタイムアウト(初期値:600 [秒])

		display	= arg.getProparty("display",display);
		debug	= arg.getProparty("display",debug);

		ftp.setDisplay(	display );		// trueは、検索状況を表示します。(初期値:false)
		ftp.setDebug(	debug   );		// デバッグ情報を標準出力に表示する(true)かしない(false)か

		// FTPConnect を初期化します。
		ftp.connect();
	}

	/**
	 * プロセスの終了を行います。最後に一度だけ、呼び出されます。
	 * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
	 *
	 * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
	 */
	public void end( final boolean isOK ) {
		if( ftp != null ) {
			ftp.disconnect();
		}
	}

	/**
	 * 引数の LineModel を処理するメソッドです。
	 * 変換処理後の LineModel を返します。
	 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
	 * null データを返します。つまり、null データは、後続処理を行わない
	 * フラグの代わりにも使用しています。
	 * なお、変換処理後の LineModel と、オリジナルの LineModel が、
	 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
	 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
	 * 各処理ごとに自分でコピー(クローン)して下さい。
	 *
	 * @param	data	オリジナルのLineModel
	 *
	 * @return	処理変換後のLineModel
	 */
	@Override	// ChainProcess
	public LineModel action( final LineModel data ) {
		inCount++ ;
		final FileLineModel fileData ;
		if( data instanceof FileLineModel ) {
			fileData = (FileLineModel)data ;
		}
		else {
			// LineModel が FileLineModel でない場合、オブジェクトを作成します。
			fileData = new FileLineModel( data );
		}

		final File localFile = fileData.getFile() ;	// LineModel から取得したファイル。
		if( ! localFile.isFile() ) {
			if( display ) { println( data.dataLine() ); }
			return data;
		}

		final String lclFileName = localFile.getAbsolutePath();

		// ファイル名は、引数ファイル名 から、 localPathを引き、remotePath を加えます。
		final String rmtFileName = remotePath + lclFileName.substring( localPathLen );

		ftp.action( command,lclFileName,rmtFileName );

		if( display ) { println( data.dataLine() ); }
		return data ;
	}

	/**
	 * プロセスの処理結果のレポート表現を返します。
	 * 処理プログラム名、入力件数、出力件数などの情報です。
	 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
	 * 形式で出してください。
	 *
	 * @return   処理結果のレポート
	 */
	public String report() {
		// 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
		return "[" + getClass().getName() + "]" + CR
//		final String report = "[" + getClass().getName() + "]" + CR
				+ TAB + "Copy Count : " + inCount   + CR
				+ TAB + "host       : " + host      + CR
				+ TAB + "user       : " + user      + CR
				+ TAB + "localPath  : " + localPath + CR
				+ TAB + "remotePath : " + remotePath ;

//		return report ;
	}

	/**
	 * このクラスの使用方法を返します。
	 *
	 * @return	このクラスの使用方法
	 * @og.rtnNotNull
	 */
	public String usage() {
		final StringBuilder buf = new StringBuilder( BUFFER_LARGE )
			.append( "Process_FileFtp は、上流から受け取った FileLineModel を処理する"					).append( CR )
			.append( "ChainProcess インターフェースの実装クラスです。"									).append( CR )
			.append( CR )
			.append( "上流から受け取った FileLineModel の ファイルから、localPath のローカル共通パスを"	).append( CR )
			.append( "remotePath のFTP共通パスに、PFT伝送します。(-command=PUT 処理のみ) "				).append( CR )
			.append( "ファイルそのものの階層構造は、維持されるため、ローカルからFTPサーバー"			).append( CR )
			.append( "へのフォルダコピーに近いイメージになります。"										).append( CR )
			.append( CR )
			.append( "Process_FileCopy との違いは、ファイルのエンコード変換は行いません。ただし、"		).append( CR )
			.append( "FTP伝送での改行コードの変換は、-mode=ASCII で指定できます。"						).append( CR )
			.append( "もうひとつ、Process_FileCopy では、inPath と outPath でのCOPY処理でしたが、"		).append( CR )
			.append( "このクラスでは、localPath と、remotePath でそれぞれの共通パスを指定します。"		).append( CR )
			.append( CR )
			.append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"		).append( CR )
			.append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"			).append( CR )
			.append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"		).append( CR )
			.append( "できれば、使用可能です。"															).append( CR )
			.append( CR ).append( CR )
			.append( getArgument().usage() ).append( CR );

		return buf.toString();
	}

	/**
	 * このクラスは、main メソッドから実行できません。
	 *
	 * @param	args	コマンド引数配列
	 */
	public static void main( final String[] args ) {
		LogWriter.log( new Process_FileFtp().usage() );
	}
}
