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

import java.io.IOException;

import org.opengion.fukurou.db.Transaction;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.URLConnect;
import static org.opengion.fukurou.util.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * 伝送要求に対してのHTTP経由で伝送処理を実行します。
 *
 * 読取方法により読み取ったデータをPOSTデータとして送信し、リモートホスト上で
 * 伝送処理を実行します。
 *
 * 処理の流れとしては以下のようになります。
 * ①HTTPで伝送処理を実行するためのサーブレットを呼び出す。
 * ②①で呼び出しされたサーブレットが、伝送処理を実行するためのオブジェクトを生成し、
 *   伝送処理を実行する。
 * ③処理終了後、伝送プロセスに制御が戻り、読取方法に基づく後処理(完了処理または
 *   エラー処理)が実行されます。
 *
 * まず①について、呼び出しされるサーブレットは、
 *   [リモート接続先URL]servlet/remoteControl?class=TransferExecWrapper になります。
 *   [リモート接続先URL]は、http://[ホスト名]:[ポート番号]/[コンテキスト名]の形式になりますが、
 *   これについては、実行対象で指定します。
 * 次に②について、サーブレット経由で実行される伝送処理について、その実行方法は、
 *   具体的には、サブクラスのクラス名に対して、このクラス(親クラス)のクラス名+"_" を除外した部分が
 *   実行方法として認識されます。
 *   具体的には、クラス名の最後の"_"(アンダーバー)以降が実行方法として認識され、
 *   例として、サブクラス名がTransferExec_HTTP_CB01の場合、接続先における実行方法は
 *   旧伝送DB登録(CB01)となります。
 *   また、リモートホスト上で実行される伝送処理の[リモート実行対象]は、元の実行対象で設定します。
 *   このことから、HTTP経由で伝送処理を実行する場合、元の実行対象には、[リモート接続先URL]と
 *   [リモート実行対象]の2つを設定する必要があります。
 *   具体的な設定方法については各サブクラスのJavaDocを参照して下さい。
 * 最後に③について、接続時にエラーが発生した場合や、レスポンスデータに対して"row_error"という
 *   文字列が存在する場合は、エラーとして処理します。
 *   それ以外の場合は、正常終了として処理します。
 *
 * HTTP接続時には、以下のポストデータが送信されます。
 * [ポストデータ]
 * ・KBREAD			(読取方法)
 * ・READOBJ		(読取対象)
 * ・READPRM		(読取パラメーター)
 * ・KBEXEC			(実行方法) ※サブクラスの最後の"_"(アンダーバー)以降の文字列
 * ・EXECDBID		(実行接続先DBID)
 * ・EXECOBJ		(リモート実行対象) ※ローカルの実行対象からリモート接続先URLを除いた文字列
 * ・EXECPRM		(実行パラメーター)
 * ・ERROR_SENDTO	(読み取り元ホストコード)
 * ・HFROM (読み取り元ホストコード)
 * ・n (データ件数)
 * ・v1～vn (データ)
 *
 * @og.group 伝送システム
 *
 * @version  5.0
 * @author   Hiroki.Nakamura
 * @since    JDK1.6
 */
public abstract class TransferExec_HTTP implements TransferExec {

	// リモート制御サーブレット名
	private static final String REMOTE_SERVLET = "servlet/remoteControl?class=TransferExecWrapper";

	/**
	 * URL接続を実行します。
	 *
	 * @param vals 伝送データ(配列)
	 * @param config 伝送設定オブジェクト
	 * @param tran トランザクションオブジェクト
	 */
	@Override
	public void execute( final String[] vals, final TransferConfig config, final Transaction tran ) {
		URLConnect conn = null;
		try {
			splitExecObj( config.getExecObj() );
			conn = new URLConnect( getRemoteHost() + REMOTE_SERVLET, TransferConfig.HTTP_AUTH_USER_PASS );
			if( config.getProxyHost() != null && config.getProxyHost().length() > 0 ) {
				conn.setProxy( config.getProxyHost(), config.getProxyPort() );
			}

			conn.setCharset( "UTF-8" );
			// ポストデータを生成します。
			conn.setPostData( getPostData( vals, config ) );

			conn.connect();

			final String readData = conn.readData();
			// 返されたデータ中に"row_error"が存在する場合はエラーとして処理します。
			if( readData != null && readData.indexOf( "row_error" ) >= 0 ) {
				throw new RuntimeException( readData );
			}
		}
		catch( IOException ex ) {
			final String errMsg = "URL接続時に例外が発生しました。";
			throw new RuntimeException( errMsg, ex );
		}
		finally {
			if( conn != null ) { conn.disconnect(); }
		}
	}

	/**
	 * ローカルの実行対象を、リモート接続先の実行対象とリモート接続先URLに分解します。
	 *
	 * @param localExecObj ローカルの実行対象
	 */
	protected abstract void splitExecObj( final String localExecObj );

	/**
	 * リモート接続先URLを返します。
	 * このメソッドは、{@link #splitExecObj(String)}の後に呼び出しする必要があります。
	 *
	 * @return リモート接続先URL
	 */
	protected abstract String getRemoteHost();

	/**
	 * リモート接続先の実行対象を返します。
	 * このメソッドは、{@link #splitExecObj(String)}の後に呼び出しする必要があります。
	 *
	 * @return 接続URL
	 */
	protected abstract String getRemoteExecObj();

	/**
	 * 伝送データ及び伝送設定をPOSTデータとしてシリアライズします。
	 *
	 * @param vals 伝送データ(配列)
	 * @param config 伝送設定オブジェクト
	 *
	 * @return ポストデータ
	 * @og.rtnNotNull
	 */
	private String getPostData( final String[] vals, final TransferConfig config ) {
		// サブクラス名から親クラス名+"_"を除いた部分を実行方法とする。
		final String kbExec = getClass().getName().replace( getClass().getSuperclass().getName() + "_", "" );

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( "KBREAD="		).append( StringUtil.urlEncode( config.getKbRead() ) )
			.append( "&READOBJ="	).append( StringUtil.urlEncode( config.getReadObj() ) )
			.append( "&READPRM="	).append( StringUtil.urlEncode( config.getReadPrm() ) )
			.append( "&KBEXEC="		).append( StringUtil.urlEncode( kbExec ) )
			.append( "&EXECDBID="	).append( StringUtil.urlEncode( config.getExecDbid() )  )
			.append( "&EXECOBJ="	).append( StringUtil.urlEncode( getRemoteExecObj() ) )
			.append( "&EXECPRM="	).append( StringUtil.urlEncode( config.getExecPrm() ) )
			.append( "&ERROR_SENDTO=").append( StringUtil.urlEncode( config.getErrorSendto() ) )
			.append( "&HFROM="		).append( StringUtil.urlEncode( config.getHfrom() ) ) ;

		if( vals != null && vals.length > 0 ) {
			buf.append( "&n=" ).append( vals.length );
			for( int i=0; i<vals.length; i++ ) {
				buf.append( "&v" ).append( i ).append( '=' );		// 6.0.2.5 (2014/10/31) char を append する。
				buf.append( StringUtil.urlEncode( vals[i] ) );
			}
		}
		else {
			buf.append( "&n=0" );
		}

		return buf.toString();
	}
}
