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

import static org.opengion.fukurou.util.StringUtil.nval;

import java.io.File;
import java.io.IOException;
import java.io.Writer;

import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.util.FileString;
import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.LogWriter;
import org.opengion.fukurou.util.URLConnect;
import org.opengion.fukurou.util.XHTMLTag;
import org.opengion.fukurou.xml.XSLT;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;

/**
 * 指定のURLに接続します。
 *
 * エンジンでは、URL にアクセスすることで、デーモンを起動したり、
 * コマンドを実行（adminメニュー）することが出来ます。
 * もちろん、検索条件を指定して、結果を取得することも可能です。
 * このタグでは、URLにアクセスして、コマンドを実行したり結果を取得できます。
 * さらに、ファイルを POST で転送したり、処理結果を XSLT変換したり出来ます。
 *
 * @og.formSample
 * ●形式：
 *     &lt;og:urlConnect
 *         url           = "http://･･･ "    必須
 *         proxyHost     = "proxy.opengion.org"
 *         proxyPort     = "8080"
 *         keys          = "command,SYSTEM_ID"
 *         vals          = "NEW,GE"
 *         useSystemUser = "true/false"     初期値：true
 *         authUserPass  = "C00000:******"  初期値：SYSTEM:******
 *         display       = "false/true"     初期値：false
 *         xslFile       = "filter.xsl"
 *         saveFile      = "outdata.xml"
 *     /&gt;
 *
 * url           : 接続するURLを指定します。必須属性です。
 * proxyHost     : proxy が存在する場合は、そのホスト名(例：proxy.opengion.org)
 * proxyPort     : proxy が存在する場合は、そのポート番号(例：8080)
 * keys,vals     : URLの指定時に、パラメータ(引数)を追加します。URLに含めても構いません。
 * postKey       : POST を使って、postFile属性のファイル内容を送信する時のキーです。
 * postFile      : POST を使って、postFile属性のファイル内容を送信します。
 *                 postFile を指定せず、postKey のみ指定して、BODY部に何か書き込めば、
 *                 そのBODY部の文字列を POSTの内容として送信します。
 * authUserPass  : Basic認証を使用する場合の接続ユーザー：パスワードを指定します。
 *                 接続時のユーザーとパスワードを、USER:PASSWD 形式 で指定します。
 *                 useSystemUser="false" で何も指定しない場合は、Basic認証を使用しません。
 * useSystemUser : Basic認証の接続ユーザー：パスワードに、システムユーザーを使用
 *                 するかどうかを指定します(初期値:true)。
 *                 true の場合は、SYSTEM:***** を使用します。
 * xslFile       : 接続先データを取得し、そのデータを XSLT変換する場合のXSLファイルを指定します。
 * display       : 接続した結果のレスポンスを画面に表示するかどうかを指定します(初期値:false)。
 *                 エンジンの場合、コマンドを投げるだけであれば、結果を取得する必要は
 *                 ありません。イメージ的には、取得データが、このタグの位置に置き換わります。
 *                 xslFile が指定されている場合、XSLT変換してセーブします。
 * saveFile      : 接続先データを取得した結果を、ファイル出力します。
 *                 display="true" と、saveFile を併用することはできません。
 *                 xslFile が指定されている場合、XSLT変換してセーブします。
 *
 * ●body：なし
 *
 * ●例：
 * アドミン関連
 * http://localhost:8823/gf/jsp/admin?COMMAND=infomation     [状況表示]
 * http://localhost:8823/gf/jsp/admin?COMMAND=close          [プール削除]
 * http://localhost:8823/gf/jsp/admin?COMMAND=loginUser      [ログインユーザー]
 * http://localhost:8823/gf/jsp/admin?COMMAND=システムパラメータ [システムパラメータ]
 *
 * 帳票デーモン
 * http://localhost:8823/gf/jsp/REP08/result.jsp?cmd=SET&period=5000&command=NEW&timerTask=org.opengion.hayabusa.report.ReportDaemon&name=ReportDaemon  デーモン起動
 * http://localhost:8823/gf/jsp/REP08/result.jsp?cmd=CANCEL&period=5000&command=NEW&timerTask=org.opengion.hayabusa.report.ReportDaemon&name=ReportDaemon  デーモン停止
 *
 *Tomcat Manager 画面
 * http://localhost:8823/manager/reload?path=/ver4 アプリケーションを再ロード
 *
 * @og.rev 3.6.0.0 (2004/09/17) 新規作成
 * @og.rev 4.1.0.0 (2007/12/22) POSTメソッドで複数キーを登録できるように属性追加
 * @og.group その他部品
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class URLConnectTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "4.0.0 (2005/08/31)" ;

	private static final long serialVersionUID = 4000 ;	// 4.0.0 (2005/01/31)

	private static final String DEFAULT_USER = "SYSTEM:MANAGER" ;

	private String		urlStr			= null;
	private String[]	keys			= null;
	private String[]	vals			= null;
	private String		xslFile			= null;
	private String		saveFile		= null;
	private String		postKey			= null;
	private String		postData		= null;		// postFile ファイルか、BODY部
	private boolean 	useSystemUser	= true;
	private String		authUserPass	= null;
	private boolean 	display			= false;
	private String		proxyHost		= null;		// 4.0.0 (2007/07/25)
	private int 		proxyPort		= -1;		// 4.0.0 (2007/07/25)
	private String		method			= "GET";	// 4.1.0.0 (2007/12/22) POSTorGET
	private boolean		errNglctFlag	= false; // 4.1.1.0 (2008/01/22) エラー無視フラグ

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @return  int
	 */
	@Override
	public int doStartTag() {
		if( postKey == null || postData != null ) {
			return( SKIP_BODY );			// Body を評価しない
		}
		else {
			return( EVAL_BODY_BUFFERED );	// Body を評価する。（ extends BodyTagSupport 時）
		}
	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @return  int
	 */
	@Override
	public int doAfterBody() {
		postData = getBodyString();

		return(SKIP_BODY);
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 4.0.1.0 (2007/12/12) PostKeys,PostVals処理を追加
	 * @return  int
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)

		if( useSystemUser ) { authUserPass = DEFAULT_USER; }

		URLConnect conn = null;
		try {

			String urlEnc = XHTMLTag.urlEncode( keys,vals );
			if( postKey != null ) { // 4.1.0.0 (2007/12/22)
				method = "POST";
				String addEnc = postKey + "=" + postData;
				urlEnc = urlEnc + "&" + addEnc; // &連結
			}

			if( ! "POST".equals( method ) ) { // 4.1.0.0 (2007/12/22)
				// String urlEnc = XHTMLTag.urlEncode( keys,vals );
				urlStr = XHTMLTag.addUrlEncode( urlStr,urlEnc );
			}
			conn = new URLConnect( urlStr,authUserPass );
			// 4.0.0 (2007/07/25) プロキシの設定追加
			if( proxyHost != null ) {
				conn.setProxy( proxyHost,proxyPort );
			}
			// if( postKey != null ) {
			//	conn.setPostData( postKey,postData );
			// }
			if( "POST".equals(method) && keys != null && vals != null ) { // 4.1.0.0 (2007/12/22)
				conn.setPostData( urlEnc );
			}
			conn.connect();
//			String data = conn.readData();

			// 出力先が、画面かファイルかを判断します。
			Writer outWriter = null;
			if( display ) {
//				outWriter = pageContext.getOut() ;		// JspWriter の取得
				outWriter = FileUtil.getNonFlushPrintWriter( pageContext.getOut() ) ;		// JspWriter の取得
			}
			else if( saveFile != null ) {
				outWriter = FileUtil.getPrintWriter( new File( saveFile ),"UTF-8" );
			}

			// 出力先が存在する場合。
			if( outWriter != null ) {
				if( xslFile != null ) {
					XSLT xslt = new XSLT();
					xslt.setXslFile( xslFile );
					xslt.setOutFile( outWriter );
//					xslt.transform( data );
					xslt.transform( conn.getReader() );
					xslt.close();
				}
				else {
//					outWriter.write( data );
					outWriter.write( conn.readData() );
				}
				Closer.ioClose( outWriter );
			}
		}
		catch( IOException ex ) {
			String errMsg = "データ取得中にエラーが発生しました。" + HybsSystem.CR
						+ " url=[" + urlStr + "]" ;
			if( errNglctFlag ) { // 4.1.1.0 (2008/01/22) エラーを無視（標準エラー出力のみ）
				LogWriter.log( errMsg );
			}
			else { // 通常は無視しない
				throw new HybsSystemException( errMsg,ex );
			}
		}
		finally {
			if( conn != null ) { conn.disconnect(); }
		}

		return(EVAL_PAGE);
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		urlStr			= null;
		proxyHost		= null;		// 4.0.0 (2007/07/25)
		proxyPort		= -1;		// 4.0.0 (2007/07/25)
		keys			= null;
		vals			= null;
		xslFile			= null;
		saveFile		= null;
		postKey			= null;
		postData		= null;
		useSystemUser	= true;
		authUserPass	= null;
		display			= false;
		method			= "GET";	// 4.1.0.0 (2007/12/22)
		errNglctFlag	= false;	// 4.1.1.0 (2008/01/22)
	}

	/**
	 * 【TAG】アクセスする ＵＲＬ を指定します(必須)。
	 *
	 * @og.tag
	 * 接続するＵＲＬを指定します。(例：http:// ･･････)
	 * ?以降のパラメータが含まれていても構いません。
	 * このURL に、keys,vals で指定されたパラメータも追加されます。
	 *
	 * @param   url String アクセスする ＵＲＬ
	 */
	public void setUrl( final String url ) {
		urlStr = nval( getRequestParameter( url ),urlStr );
	}

	/**
	 * 【TAG】プロキシ経由で接続する場合の、プロキシホスト名を指定します。
	 *
	 * @og.tag
	 * 接続先が、プロキシ経由の場合、プロキシのホスト名を指定します。
	 * 例：proxy.opengion.org
	 *
	 * @param   host String プロキシホスト名
	 */
	public void setProxyHost( final String host ) {
		proxyHost = nval( getRequestParameter( host ),proxyHost );
		useSystemUser = false;	// プロキシ接続時は、システムユーザーは使えません。
	}

	/**
	 * 【TAG】プロキシ経由で接続する場合の、プロキシポート番号を指定します。
	 *
	 * @og.tag
	 * 接続先が、プロキシ経由の場合、プロキシのポート番号を指定します。
	 * 例：8080
	 *
	 * @param   port String プロキシポート番号
	 */
	public void setProxyPort( final String port ) {
		proxyPort = nval( getRequestParameter( port ),proxyPort );
	}

	/**
	 * 【TAG】アクセスパラメータキーをCSV形式で複数指定します。
	 *
	 * @og.tag
	 * アクセスする ＵＲＬに追加するパラメータのキーを指定します。
	 * カンマ区切りで複数指定できます。
	 * vals 属性には、キーに対応する値を、設定してください。
	 * 例:<b>keys="command,SYSTEM_ID"</b> vals="NEW,GE"
	 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。
	 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。
	 *
	 * @param	key リンク先に渡すキー
	 * @see		#setVals( String )
	 */
	public void setKeys( final String key ) {
		keys = getCSVParameter( key );
	}

	/**
	 * 【TAG】keys属性に対応する値をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * キーに設定した値を、カンマ区切り文字で複数して出来ます。
	 * 指定順序は、キーと同じにしておいて下さい。
	 * 例:<b>keys="command,SYSTEM_ID"</b> vals="NEW,GE"
	 * 分解方法は、CSV変数を先に分解してから、getRequestParameter で値を取得します。
	 * こうしないとデータ自身にカンマを持っている場合に分解をミスる為です。
	 *
	 * @param	val names属性に対応する値
	 * @see		#setKeys( String )
	 */
	public void setVals( final String val ) {
		vals = getCSVParameter( val );
	}

	/**
	 * 【TAG】送信メソッドを指定します。(GET/POST)
	 *
	 * URLConnectTagのメソッドの初期設定はGETです。
	 * ここで"POST"（大文字）を指定するとkyes,valsの値セットをPOSTで送信します。
	 * （postKeyが設定されている場合はこの値に関係なくPOSTです）
	 *
	 * @og.rev 4.1.0.0 (2007/12/22) 新規作成
	 * @param post_get String
	 */
	public void setMethod ( final String post_get ) {
		method = nval( getRequestParameter( post_get ), method );
	}

	/**
	 * 【TAG】Basic認証で接続するユーザーにSYSTEMユーザーを使用するかどうかを指定します(初期値：true)
	 *
	 * @og.tag
	 * useSystemUser="true"(初期値) の場合、URL接続時のコネクションに、Basic認証を
	 * 使用しますが、その時のユーザーにシステムユーザー(SYSTEM)を使用します。
	 * useSystemUser="false"の場合は、authUserPass で指定したユーザー：パスワードを
	 * 使用します。authUserPass で、何も指定されなかった場合は、Basic認証を使用しません。
	 *
	 * @param   flag String true:SYSTEMユーザー認証する(初期値)/false:この接続のユーザーで認証する。
	 * @see #setAuthUserPass( String )
	 */
	public void setUseSystemUser( final String flag ) {
		useSystemUser = nval( getRequestParameter( flag ),useSystemUser );
	}

	/**
	 * 【TAG】Basic認証を使用して接続する場合のユーザー:パスワードを指定します(初期値:null)。
	 *
	 * @og.tag
	 * 接続時のユーザーとパスワードを、USER:PASSWD 形式で指定します。
	 * useSystemUser="false"の場合は、ここで指定したユーザーとパスワードを使用します。
	 * その場合に、何も指定しない場合は、Basic認証を使用しません。
	 *
	 * @param   userPass String 接続のユーザーとパスワード(USER:PASSWD 形式)
	 * @see #setUseSystemUser( String )
	 */
	public void setAuthUserPass( final String userPass ) {
		authUserPass = nval( getRequestParameter( userPass ),authUserPass );
	}

	/**
	 * 【TAG】接続の結果を表示する場合にXSLT変換する場合のファイルを指定します。
	 *
	 * @og.tag
	 *
	 * 接続先のデータが、XML形式の場合、そのままでは、画面出力できない場合が
	 * あります。通常は、HTML形式に変換しますが、その変換に、 XSL ファイルを
	 * 指定することが可能です。
	 * display="true" の場合や、saveFile を指定した場合に、適用されます。
	 *
	 * @param   file String 接続の結果を表示する場合にXSLT変換する場合のファイル
	 * @see #setSaveFile( String )
	 * @see #setDisplay( String )
	 */
	public void setXslFile( final String file ) {
		xslFile = HybsSystem.url2dir( nval( getRequestParameter( file ),xslFile ) );
	}

	/**
	 * 【TAG】接続の結果を表示するかどうかを指定します(初期値:false)。
	 *
	 * @og.tag
	 * true で、接続結果を表示します。 false では、何も表示しません(初期値:false)
	 * 接続結果を表示する使い方より、admin 画面に接続して、キャッシュクリアするような
	 * 使い方が多いと考え、初期値は、false になっています。
	 * xslFile が指定されている場合、XSLT変換して画面表示します。
	 * display="true" と、saveFile を併用することはできません。
	 *
	 * @param   flag String 接続の結果を表示するかどうか(初期値:false)
	 * @see #setSaveFile( String )
	 * @see #setXslFile( String )
	 */
	public void setDisplay( final String flag ) {
		display = nval( getRequestParameter( flag ),display );

		if( display && saveFile != null ) {
			String errMsg = "display=\"true\" と、saveFile を併用することはできません。";
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【TAG】接続の結果をファイルに保存します。
	 *
	 * @og.tag
	 * 接続先のデータを受け取って、ファイルに保存します。その場合、
	 * xslFile が指定されている場合、XSLT変換してセーブします。
	 * display="true" と、saveFile を併用することはできません。
	 *
	 * @param   file String 接続の結果を表示する場合にXSLT変換する場合のファイル
	 * @see #setXslFile( String )
	 * @see #setDisplay( String )
	 */
	public void setSaveFile( final String file ) {
		saveFile = HybsSystem.url2dir( nval( getRequestParameter( file ),saveFile ) );

		if( display ) {
			String errMsg = "display=\"true\" と、saveFile を併用することはできません。";
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【TAG】POST を使って、postFile属性のファイル内容を送信する時のキーを指定します。
	 *
	 * @og.tag
	 * 接続先にパラメータ(引数)を投げる場合に、POST を使用できます。
	 * そのときの キーをここで指定します。
	 * POSTするデータは、postFileで指定されたファイルか、BODY部に記述された文字列です。
	 *
	 * @param   key String POST を使って、postFile属性のファイル内容を送信する時のキー
	 * @see  #setPostFile( String )
	 */
	public void setPostKey( final String key ) {
		postKey = nval( getRequestParameter( key ),postKey );
	}

	/**
	 * 【TAG】POST を使って、postFile属性のファイル内容を送信します。
	 *
	 * @og.tag
	 * 接続先にパラメータ(引数)を投げる場合に、POST を使用できます。
	 * そのときの 送信データのファイルをここで指定します。
	 * postKey のみ指定されて、postFile が指定されない場合は、BODY部を送信します。
	 *
	 * @param   file String 接続の結果を表示する場合にXSLT変換する場合のファイル
	 * @see  #setPostKey( String )
	 */
	public void setPostFile( final String file ) {
		String postFile = nval( getRequestParameter( file ),null );

		if( postFile != null ) {
			FileString fileStr = new FileString();
			fileStr.setFilename( HybsSystem.url2dir( postFile ) );
			postData = fileStr.getValue();
		}
	}
	
	/**
	 * 【TAG】(通常は使いません) 接続エラーを無視する場合にtrueとします。（初期値false)
	 * 
	 * @og.tag
	 * trueにするとConnectで発生したエラーを投げずに処理を続行します。
	 * （標準エラー出力にエラー内容は出力されます）
	 * 接続エラーが発生しても処理を中断したくない場合に設定します。
	 * 
	 * @og.rev 4.1.1.0 (2008/01/22) 新規追加
	 * 
	 * @param flag String エラーを無視する場合にtrue
	 */
	public void setErrNeglect( final String flag ) {
		errNglctFlag = nval( getRequestParameter( flag ), errNglctFlag );
	}

	/**
	 * タグの名称を、返します。
	 * 自分自身のクラス名より、自動的に取り出せないため、このメソッドをオーバーライドします。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 新規追加
	 *
	 * @return  タグの名称
	 */
	protected String getTagName() {
		return "urlConnect" ;
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION		)
				.println( "urlStr"			,urlStr			)
				.println( "useSystemUser"	,useSystemUser	)
				.println( "authUserPass"	,authUserPass	)
				.println( "display"			,display		)
				.println( "xslFile"			,xslFile		)
				.println( "Other..."		,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}
