/*
 * 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 java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.opengion.fukurou.system.Closer;
import org.opengion.fukurou.util.ArraySet;
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.HttpConnect;
import org.opengion.fukurou.util.JSONScan;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.ToString;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.db.DBTableModelUtil;

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

/**
 * IOr (Information Organizer) に接続し、取得したデータベースを表示するタグです。
 *
 * IorQueryParamTag に検索条件を指定して、結果を取得することも可能です。
 * 検索結果は、DBTableModel にセットされます。
 * このタグに、データ(DBTableModel)と、コントローラ(ViewForm)を与えて、
 * 外部からコントロールすることで、テキストフィールドやテーブルの形で表示したり、
 * 入力可/不可、表示可/不可の設定を行うことができます。
 *
 * SystemData の USE_SQL_INJECTION_CHECK が true か、quotCheck 属性が true の場合は、
 * SQLインジェクション対策用のシングルクォートチェックを行います。リクエスト引数に
 * シングルクォート(')が含まれると、エラーになります。
 * 同様にUSE_XSS_CHECKがtrueか、xssCheck属性がtrueの場合は、
 * クロスサイトススクリプティング(XSS)対策のためless/greater than signのチェックを行います。
 *
 * 実行後にリクエストパラメータに以下の値がセットされます。
 *   DB.COUNT     : 検索結果の件数
 *   DB.ERR_CODE  : 検索結果のエラーコード
 *
 * ※ このタグは、Transaction タグの対象です。
 *
 * @og.formSample
 * ●形式：
 *     &lt;og:iorQuery
 *         url           = "http://･･･ "    必須
 *         authURL       = "http://･･･ "    必須
 *         authUserPass  = "admin:******"   必須
 *         appliName     = "データテーブル名"
 *         callMethod    = "getReportInfo"
 *     /&gt;
 *
 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
 *
 * ●Tag定義：
 *   &lt;og:iorQuery
 *       url              ○【TAG】アクセスする URL を指定します (必須)
 *       proxyHost          【TAG】プロキシ経由で接続する場合の、プロキシホスト名を指定します
 *       proxyPort          【TAG】プロキシ経由で接続する場合の、プロキシポート番号を指定します
 *       timeout            【TAG】通信リンクのオープン時に、指定された秒単位のタイム・アウト値を使用します
 *                                  (初期値:URL_CONNECT_TIMEOUT[={@og.value SystemData#URL_CONNECT_TIMEOUT}])
 *       keys               【TAG】アクセスパラメータキーをCSV形式で複数指定します
 *       vals               【TAG】keys属性に対応する値をCSV形式で複数指定します
 *       authURL          ○【TAG】JSONコードで認証するURLを指定します (必須)
 *       authUserPass     ○【TAG】Basic認証を使用して接続する場合のユーザー:パスワードを指定します (必須)
 *       companyId          【TAG】企業IDを指定します
 *       appliName          【TAG】アプリケーションの名前を指定します
 *       callMethod         【TAG】関数名を指定します
 *       display            【TAG】接続の結果を表示するかどうかを指定します (初期値:false)
 *       saveFile           【TAG】接続の結果をファイルに保存します
 *       loadFile           【TAG】ファイルからURL接続結果に相当するデータを読み取ります
 *       command            【TAG】コマンド (NEW,RENEW)をセットします
 *       scope              【TAG】キャッシュする場合のスコープ[request/page/session/application]を指定します (初期値:session)
 *       displayMsg         【TAG】検索結果を画面上に表示するメッセージリソースIDを指定します (初期値:VIEW_DISPLAY_MSG[=])
 *       notfoundMsg        【TAG】検索結果がゼロ件の場合に表示するメッセージリソースIDを指定します
 *                                  (初期値:MSG0077[対象データはありませんでした])
 *       stopZero           【TAG】検索結果が０件のとき処理を続行するかどうか[true/false]を指定します (初期値:false[続行する])
 *       tableId            【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
 *       quotCheck          【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します
 *                                  (初期値:USE_SQL_INJECTION_CHECK)
 *       xssCheck           【TAG】リクエスト情報の HTMLTag開始/終了文字(&gt;&lt;) 存在チェックを実施するかどうか[true/false]を設定します
 *                                  (初期値:USE_XSS_CHECK[=true])
 *       mainTrans          【TAG】(通常は使いません)タグで処理される処理がメインとなるトランザクション処理かどうかを指定します (初期値:false)
 *       useBeforeHtmlTag   【TAG】処理時間(queryTime)などの情報出力[true:有効/false:無効]を指定します (初期値:true)
 *       useTimeView        【TAG】処理時間を表示する TimeView を表示するかどうかを指定します
 *                                  (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])
 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します (初期値:null)
 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します (初期値:null)
 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます (初期値:判定しない)
 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます (初期値:判定しない)
 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます (初期値:判定しない)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します (初期値:false)
 *
 *   &gt;   ... Body ...
 *   &lt;/og:iorQuery&gt;
 *
 * ●使用例
 *     &lt;og:IorQuery
 *         url           = "http://･･･ "
 *         authURL       = "http://･･･ "
 *         authUserPass  = "admin:******"
 *         appliName     = "データテーブル名"
 *         callMethod    = "getReportInfo"
 *     &gt;
 *         &lt;og:iorQueryParam
 *             key  = "where"  value  = "{'PN':'{@PN}%','TANI':'{@TANI}'}"  /&gt;
 *         &lt;/og:iorQueryParam
 *     &lt;/og:IorQuery ･････ &gt;
 *
 * @og.rev 8.0.2.0 (2021/11/30) 新規作成
 * @og.group その他部品
 *
 * @version  8.0
 * @author   LEE.M
 * @since    JDK17.0,
 */
public class IorQueryTag extends CommonTagSupport {
	/** このプログラムのVERSION文字列を設定します。 {@value} */
	private static final String VERSION = "8.0.2.0 (2021/11/30)" ;
	private static final long serialVersionUID = 802020211130L ;

	public static final String CMD_NEW		= "NEW";											// コマンド(新規)
	public static final String CMD_RENEW	= "RENEW";											// コマンド(再検索)

	// String配列 から、Setに置き換えます。
	private static final Set<String> COMMAND_SET = new ArraySet<>( CMD_NEW, CMD_RENEW );
	private static final String ERR_MSG_ID	= HybsSystem.ERR_MSG_KEY;							// エラーメッセージID

	private transient DBTableModel table;														// テーブルモデル
	private transient ErrorMessage errMessage;													// エラーメッセージ
	private transient Map<String,String> mapParam = new LinkedHashMap<>();						// パラメータ

	private int			stScan				= 0;												// 検索範囲の開始位置
	private int			edScan				= 0;												// 検索範囲の終了位置
	private int			executeCount		= 0;												// 検索件数
	private int			errCode				= ErrorMessage.OK;									// エラーコード
	private int			timeout				= HybsSystem.sysInt( "URL_CONNECT_TIMEOUT" );
	private int			proxyPort			= HybsSystem.sysInt( "HTTP_PROXY_PORT" );
	private long		dyStart				;													// 現在時刻

	private String		urlStr				;
	private String		authURL				;
	private String		authUserPass		;													// ユーザー:パスワード
	private String		user				;													// ユーザー
	private String		pass				;													// パスワード
	private String		appliName			;
	private String		callMethod			;
	private String		saveFile			;
	private String		loadFile			;
	private String		postData			;													// BODY部
	private String		rtnData				;													// 取得データ
	private String		notfoundMsg			= "MSG0077";										// 対象データはありませんでした。
	private String		command				= CMD_NEW;											// コマンド
	private String		tableId				= HybsSystem.TBL_MDL_KEY;							// テーブルモデル
	private String		proxyHost			= HybsSystem.sys( "HTTP_PROXY_HOST" );
	private String		companyId			= HybsSystem.sys( "IOR_COMPANYID" );
	private String		fileURL				= HybsSystem.sys( "FILE_URL" );
	private String		displayMsg			= HybsSystem.sys( "VIEW_DISPLAY_MSG" );				// ディスプレイメッセージ

	private String[]	keys				;
	private String[]	vals				;

	private boolean		display				;
	private boolean		stopZero			;
	private boolean		isMainTrans			= true;												// DBLastSqlの処理の見直し
	private boolean		useBeforeHtmlTag	= true;												// 処理時間(queryTime)などの情報出力の有効/無効を指定
	private boolean		quotCheck			= HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );	// クオートチェック
	private boolean		xssCheck			= HybsSystem.sysBool( "USE_XSS_CHECK" );			// XSSチェック
	private boolean		useTimeView			= HybsSystem.sysBool( "VIEW_USE_TIMEBAR" );			// タイムバーの使用有無

	/**
	 * デフォルトコンストラクター
	 *
	 */
	// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
	public IorQueryTag() { super(); }

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doStartTag() {
		if( useTag() ) {
			useXssCheck( xssCheck );

			dyStart = System.currentTimeMillis();								// 現在時刻

			if( ! check( command, COMMAND_SET ) ) {
				table = (DBTableModel)getObject( tableId );
				if( table == null )	{ executeCount = 0; }
				else				{ executeCount = table.getRowCount(); }
				return SKIP_BODY;
			}

			useMainTrans( isMainTrans );
			startQueryTransaction( tableId );

			// 削除するのは、scope="session" の場合のみ。
			if( "session".equals( getScope() ) ) {
				removeSessionAttribute( tableId );
				removeSessionAttribute( HybsSystem.VIEWFORM_KEY );
			}

			// 読取ファイル指定無し
			if( loadFile == null ) {
				final String[] prm = StringUtil.csv2Array( authUserPass, ':' ,2 );		// ユーザー:パスワード
				user = prm[0];															// ユーザー
				pass = prm[1];															// パスワード

				// 企業ID
				if( companyId != null ) { mapParam.put( "company_id", companyId ); }
				// ユーザーID
				if( user != null ) { mapParam.put( "user_id", user ); }
				// セッションID
				if( user != null ) { mapParam.put( "session_id", "$session_id$" ); }
				// アプリ名
				if( appliName != null ) { mapParam.put( "report_name", appliName ); }
				// メソッド
				if( callMethod != null ) { mapParam.put( "method", callMethod ); }
			}
			return EVAL_BODY_BUFFERED;											// Body を評価する (extends BodyTagSupport 時)
		}
		return SKIP_BODY;														// Body を評価しない
	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @return	後続処理の指示(SKIP_BODY)
	 */
	@Override
	public int doAfterBody() {
		// useQuotCheck() によるSQLインジェクション対策
		useQuotCheck( quotCheck );

		postData = getBodyString();

		return SKIP_BODY;
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();
		if( !useTag() ) { return EVAL_PAGE ; }

		if( check( command, COMMAND_SET ) ) {
			// データ取得を要求します。
			getData();

			// 実行結果のHTTPステータスコードを設定します。
			getStts();

			// 異常以外でデータがある場合のみテーブルモデル作成します。
			if( errCode < ErrorMessage.NG && Pattern.compile( "rows.*cols" ).matcher(rtnData).find() ) {
				table = makeDBTable();
			}

			// 検索結果の件数を、"DB.COUNT" キーでリクエストにセットします。
			setRequestAttribute( "DB.COUNT"   , String.valueOf( executeCount ) );
			// 検索結果を、"DB.ERR_CODE" キーでリクエストにセットします。
			setRequestAttribute( "DB.ERR_CODE", String.valueOf( errCode ) );

			if( errCode > 0 ) { return SKIP_PAGE ; }

			// テーブルモデル作成後に、発生するエラーをセットします。
			setErr();
		}

		// 件数０件かつ stopZero = true
		final int rtnCode = executeCount == 0 && stopZero ? SKIP_PAGE : EVAL_PAGE ;

		// 処理時間(queryTime)などの情報出力の有効/無効を指定します。
		if( useTimeView && useBeforeHtmlTag ) {
			final long dyTime = System.currentTimeMillis() - dyStart;
			jspPrint( "<div id=\"queryTime\" value=\"" + (dyTime) + "\"></div>" );
		}

		return rtnCode;
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 */
	@Override
	protected void release2() {
		super.release2();
		table				= null;
		errMessage			= null;
		mapParam			= new LinkedHashMap<>();
		stScan				= 0;
		edScan				= 0;
		executeCount		= 0;
		errCode				= ErrorMessage.OK;
		timeout				= HybsSystem.sysInt( "URL_CONNECT_TIMEOUT" );
		proxyPort			= HybsSystem.sysInt( "HTTP_PROXY_PORT" );
		dyStart				= 0;
		urlStr				= null;
		authURL				= null;
		authUserPass		= null;
		user				= null;
		pass				= null;
		appliName			= null;
		callMethod			= null;
		saveFile			= null;
		loadFile			= null;
		postData			= null;
		rtnData				= null;
		notfoundMsg			= "MSG0077";
		command				= CMD_NEW;
		tableId				= HybsSystem.TBL_MDL_KEY;
		proxyHost			= HybsSystem.sys( "HTTP_PROXY_HOST" );
		companyId			= HybsSystem.sys( "IOR_COMPANYID" );
		fileURL				= HybsSystem.sys( "FILE_URL" );
		displayMsg			= HybsSystem.sys( "VIEW_DISPLAY_MSG" );
		keys				= null;
		vals				= null;
		display				= false;
		stopZero			= false;
		isMainTrans			= true;
		useBeforeHtmlTag	= true;
		quotCheck			= HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );
		xssCheck			= HybsSystem.sysBool( "USE_XSS_CHECK" );
		useTimeView			= HybsSystem.sysBool( "VIEW_USE_TIMEBAR" );
	}

	/**
	 * IOr からデータ取得を要求します。
	 */
	private void getData() {
		final StringBuilder buf = new StringBuilder(BUFFER_MIDDLE);

		// 読取ファイル指定有り
		if( loadFile != null ) {
			final Charset UTF_8 = StandardCharsets.UTF_8;
			try {
				for (final String text : Files.readAllLines( Paths.get(loadFile), UTF_8 )) {
					buf.append( text );
					jspPrint(text);
				}
				rtnData = buf.toString();
			}
			catch( final IOException ex ) {
				final String errMsg = "loadFile 処理中でエラーが発生しました。"	+ CR
							+ "\t " + ex.getMessage()							+ CR ;
				throw new HybsSystemException( errMsg );
			}
		}
		// 読取ファイル指定無し
		else {
			if( postData == null || postData.isEmpty() ){
				postData = JSONScan.map2Json( mapParam );
			}

			HttpConnect conn = null;
			Writer outWriter = null;
			try {
				conn = connect();

				rtnData = conn.readData();
				// 実行結果のステータスコードが'200'(正常)ではないとき、例外を発生させます。
				if( conn.getCode() != 200 ) { throw new Throwable(); }

				if( display ) {
					outWriter = FileUtil.getNonFlushPrintWriter( pageContext.getOut() ) ;	// JspWriter の取得
				}
				else if( saveFile != null ) {
					outWriter = FileUtil.getPrintWriter( new File( saveFile ), "UTF-8" );
				}

				// Unicode文字列から元の文字列に変換します。
				rtnData = convertToOiginal( rtnData );

				// 出力先が存在する場合
				if( outWriter != null ) {
					outWriter.write( rtnData );
				}
			}
			catch( final Throwable th ) {
				final String errMsg = "データ取得中にエラーが発生しました。"	+ CR
							+ " url=[" + urlStr + "]"							+ CR
							+ " message=[" + ( conn == null ? "NO_CONNECTION" : conn.getMessage() ) + "]" + CR
							+ " Exception=[" + th.getMessage() + "]" ;
				throw new HybsSystemException( errMsg, th );
			}
			finally {
				Closer.ioClose( outWriter );
			}
		}
	}

	/**
	 * URLに対して接続を行います。
	 *
	 * @return	接続オブジェクト
	 * @throws	IOException 入出力エラーが発生したとき
	 */
	private HttpConnect connect() throws IOException {
		// HttpConnect は、後付で引数を渡せます。
		HttpConnect conn = new HttpConnect( urlStr, authUserPass );
		conn.setDebug( isDebug() );

		if( keys != null ) {
			for( int i=0; i<keys.length; i++ ) {
				conn.addRequestProperty( keys[i] , vals[i] );
			}
		}

		conn.usePost( true );

		// プロキシ
		if( proxyHost != null ) {
			conn.setProxy( proxyHost,proxyPort );
		}
		// JSONによるフェーズ認証
		if( authUserPass != null && authURL != null ) {
			final String authJson = "{\"userInfo\":{\"companyId\":\"" + companyId + "\",\"userId\":\"" + user + "\",\"password\":\"" + pass + "\"}}";
			conn.setAuthJson( authJson , authURL );
		}
		// 接続タイムアウト時間
		if( timeout >= 0 ) {
			conn.setTimeout( timeout );
		}
		// JSONコードでリクエストするパラメータを指定
		if( postData != null ) {
			conn.setReqJson( postData );
		}
		return conn;
	}

	/**
	 * Unicode文字列から元の文字列に変換します。(例："￥u3042" → "あ")
	 *
	 * @param	unicode Unicode文字列("\u3042")
	 * @return	通常の文字列
	 */
	private String convertToOiginal( final String unicode ) {
		final StringBuilder rtn = new StringBuilder( unicode );

		int st = rtn.indexOf( "\\u" );
		while( st >= 0 ) {
			final int ch = Integer.parseInt( rtn.substring( st+2,st+6 ),16 );
			rtn.replace( st,st+6, Character.toString( (char)ch ) );

			st = rtn.indexOf( "\\u",st + 1 );
		}
		return rtn.toString();
	}

	/**
	 * IOr の検索結果から、HTTPステータスコードを設定します。
	 */
	private void getStts() {
		// 例：{"status": 200, "message": "OK", "sessionInfo": "51b16", "data" …
		stScan = 0;																// 検索範囲の開始位置
		edScan = rtnData.indexOf("\"data\"");									// 検索範囲の終了位置

		final String dtStts = rtnData.substring( stScan, edScan );
		// キーと値をマッピングします。
		final Map<String,String> mapStts = JSONScan.json2Map( dtStts );
		final String stts = mapStts.get( "status" );							// ステータス
		final String msg  = mapStts.get( "message" );							// エラーメッセージ

		// ステータスが'200'(正常)ではない
		if( ! stts.equals( String.valueOf( 200 ) ) ) {
			final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
			errCode = ErrorMessage.NG;
			errMessage = new ErrorMessage( msg );
			errMessage.addMessage( 0,ErrorMessage.NG,stts,msg );

			// TaglibUtil.makeHTMLErrorTable メソッドを利用
			final String err = TaglibUtil.makeHTMLErrorTable( errMessage,getResource() );
			if( err != null && err.length() > 0 ) {
				buf.append( err );
				setSessionAttribute( ERR_MSG_ID,errMessage );
			}
			else if( CMD_NEW.equals( command ) ) {
				removeSessionAttribute( ERR_MSG_ID );
			}
			String label = buf.toString();
			jspPrint( label );
			// エラーメッセージを、"DB.ERR_MSG" キーでリクエストにセットします。
			setRequestAttribute( "DB.ERR_MSG", label );
		}
	}

	/**
	 * IOr の 検索結果から、値を取り出し、DBTableModel を作成します。
	 *
	 * @return	テーブルモデル
	 */
	private DBTableModel makeDBTable() {

		final DBTableModel table = DBTableModelUtil.newDBTable();
		int i = 0;

		// ---------------------------------------------------------------------
		// テーブルモデル(列)
		// 例："headers": [{"display_label": "品目番号", "display": "PN"}, … ],"rows" …
		// ---------------------------------------------------------------------
		stScan = rtnData.indexOf("\"headers\"", edScan );						// 検索範囲の開始位置
		edScan = rtnData.indexOf("\"rows\"", stScan );							// 検索範囲の終了位置

		// 中括弧({})で分割
		final JSONScan scanClm = new JSONScan( rtnData, '{', '}', stScan, edScan );
		final int cntClm = scanClm.countBlock();
		table.init( cntClm );

		while( scanClm.hasNext() ) {
			final String clms = scanClm.next();

			// キーと値をマッピングします。
			final Map<String,String> mapClm = JSONScan.json2Map( clms );
			final String clmLbl = mapClm.get( "display_label" );				// ラベル(例：品目番号)
			final String clmKey = mapClm.get( "display" );						// キー(例：PN)

			// テーブルモデルに列を追加します。
			final DBColumn dbColumn = getResource().makeDBColumn( clmKey, clmLbl );
			table.setDBColumn( i++, dbColumn );
		}

		// ---------------------------------------------------------------------
		// テーブルモデル(行)
		// 例："rows": [{"cols": [1, 1, "PNT", "20211130", 32.4, "kg"]}, … ]}}
		// ---------------------------------------------------------------------
		stScan = rtnData.indexOf( "\"cols\"", edScan );							// 検索範囲の開始位置
		edScan = rtnData.length();												// 検索範囲の終了位置

		// 大括弧([])で分割
		final JSONScan scanRow = new JSONScan( rtnData, '[', ']', stScan, edScan );
		while( scanRow.hasNext() ) {
			final String rows = scanRow.next();
			String[] vals = JSONScan.json2Array( rows );
			// テーブルモデルに行の値を追加します。
			table.addColumnValues( vals );
			executeCount++;														// 検索件数
		}
		return table ;
	}

	/**
	 * テーブルモデル作成後に、発生するエラーをセットします。
	 */
	private void setErr() {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		if( CMD_NEW.equals( command ) ) {
			// 検索結果を画面上に表示します。
			if( executeCount > 0 && displayMsg != null && displayMsg.length() > 0 ) {
				buf.append( executeCount )
					.append( getResource().getLabel( displayMsg ) )
					.append( BR );
			}
			// 検索結果がゼロ件の場合に画面上に表示します。
			else if( executeCount == 0 && notfoundMsg != null && notfoundMsg.length() > 0 ) {
				buf.append( getResource().getLabel( notfoundMsg ) )
					.append( BR );
			}
		}

		// ｢ERR0041:検索処理中に割り込みの検索要求がありました｣エラーを、標準のErrorMessageに追加するようにします。
		if( table != null && ! commitTableObject( tableId, table ) ) {
			if( errMessage == null ) { errMessage = new ErrorMessage( "iorQueryTag Error!" ); }
			// ERR0041:検索処理中に割り込みの検索要求がありました。処理されません。
			errMessage.addMessage( 0,ErrorMessage.NG,"ERR0041" );
			errCode = ErrorMessage.NG;
		}

		// TaglibUtil.makeHTMLErrorTable メソッドを利用します。
		final String err = TaglibUtil.makeHTMLErrorTable( errMessage,getResource() );
		if( err != null && err.length() > 0 ) {
			buf.append( err );
			setSessionAttribute( ERR_MSG_ID,errMessage );
		}
		else if( CMD_NEW.equals( command ) ) {
			removeSessionAttribute( ERR_MSG_ID );
		}
		String label = buf.toString();
		jspPrint( label );
		// エラーメッセージをリクエスト変数で持つようにして
		setRequestAttribute( "DB.ERR_MSG", label );
	}

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

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

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

	/**
	 * 【TAG】接続タイムアウト時間を(秒)で指定します
	 *        (初期値:URL_CONNECT_TIMEOUT[={@og.value SystemData#URL_CONNECT_TIMEOUT}])。
	 *
	 * @og.tag
	 * 実際には、java.net.URLConnection#setConnectTimeout(int) に 1000倍して設定されます。
	 * 0 は、無限のタイムアウト、マイナスは、設定しません。(つまりJavaの初期値のまま)
	 * (初期値:システム定数のURL_CONNECT_TIMEOUT[={@og.value SystemData#URL_CONNECT_TIMEOUT}])。
	 *
	 * @param	tout	タイムアウト時間(秒) (ゼロは、無制限)
	 * @see		org.opengion.fukurou.util.HttpConnect#setTimeout(int)
	 * @see		java.net.URLConnection#setConnectTimeout(int)
	 */
	public void setTimeout( final String tout ) {
		timeout = nval( getRequestParameter( tout ),timeout );
	}

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

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

	/**
	 * 【TAG】JSONコードで認証するURLを指定します。
	 *
	 * @og.tag
	 * JSONコードで認証するURLを指定します。
	 *
	 * @param	url	JSONコードで認証するURL
	 */
	public void setAuthURL( final String url ) {
		authURL = nval( getRequestParameter( url ), authURL );
	}

	/**
	 * 【TAG】Basic認証を使用して接続する場合のユーザー:パスワードを指定します(初期値:null)。
	 *
	 * @og.tag
	 * 接続時のユーザーとパスワードを、USER:PASSWD 形式で指定します。
	 *
	 * @param	userPass	ユーザーとパスワード (USER:PASSWD形式)
	 */
	public void setAuthUserPass( final String userPass ) {
		authUserPass = nval( getRequestParameter( userPass ),authUserPass );
	}

	/**
	 * 【TAG】企業IDを指定します。
	 *
	 * @og.tag
	 * 企業IDを指定します。
	 *
	 * @param	compId	企業ID
	 */
	public void setCompanyId( final String compId ) {
		companyId = nval( getRequestParameter( compId ),companyId );
	}

	/**
	 * 【TAG】アプリケーションの名前を指定します。
	 *
	 * @og.tag
	 * アプリケーションの名前を指定します。
	 *
	 * @param	appName	データテーブル情報
	 */
	public void setAppliName( final String appName ) {
		appliName = nval( getRequestParameter( appName ),appliName );
	}

	/**
	 * 【TAG】関数名を指定します。
	 *
	 * @og.tag
	 * 関数名を指定します。
	 *
	 * @param	callMh	関数名
	 */
	public void setCallMethod( final String callMh ) {
		callMethod = nval( getRequestParameter( callMh ),callMethod );
	}

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

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

	/**
	 * 【TAG】接続の結果をファイルに保存します。
	 *
	 * @og.tag
	 * 接続先のデータを受け取って、ファイルに保存します。その場合、
	 * display="true" と、saveFile を併用することはできません。
	 *
	 * @param	file	保存先ファイル
	 * @see		#setDisplay( String )
	 */
	public void setSaveFile( final String file ) {
		saveFile = nval( getRequestParameter( file ),saveFile );
		if( saveFile != null ) {
			saveFile = HybsSystem.url2dir( StringUtil.urlAppend( fileURL,saveFile ) );
		}

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

	/**
	 * 【TAG】ファイルからURL接続結果に相当するデータを読み取ります。
	 *
	 * @og.tag
	 * 主にデバッグ用として使われます。
	 * display="true" と、loadFile を併用することはできません。
	 *
	 * @param	file	検索するファイル
	 */
	public void setLoadFile( final String file ) {
		loadFile = nval( getRequestParameter( file ),loadFile );
		if( loadFile != null ) {
			loadFile = HybsSystem.url2dir( StringUtil.urlAppend( fileURL,loadFile ) );
		}

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

	/**
	 * 【TAG】コマンド (NEW,RENEW)をセットします。
	 *
	 * @og.tag
	 * コマンドは、HTMLから(get/post)指定されますので、
	 * CMD_xxx で設定されるフィールド定数値のいづれかを指定できます。
	 *
	 * @param	cmd	コマンド (public static final 宣言されている文字列)
	 * @see		<a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.QueryTag.CMD_NEW">コマンド定数</a>
	 */
	public void setCommand( final String cmd ) {
		final String cmd2 = getRequestParameter( cmd );
		if( cmd2 != null && cmd2.length() >= 0 ) { command = cmd2.toUpperCase( Locale.JAPAN ); }
	}

	/**
	 * 【TAG】検索結果を画面上に表示するメッセージリソースIDを指定します
	 *        (初期値:VIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}])。
	 *
	 * @og.tag
	 * ここでは、検索結果の件数や登録された件数をまず出力し、
	 * その次に、ここで指定したメッセージをリソースから取得して表示します。
	 * 件数を表示させる場合は、displayMsg = "MSG0033"[　件検索しました] をセットしてください。
	 * 表示させたくない場合は, displayMsg = "" をセットしてください。
	 * (初期値:システム定数のVIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}])。
	 *
	 * @param	id	表示メッセージID
	 * @see		org.opengion.hayabusa.common.SystemData#VIEW_DISPLAY_MSG
	 */
	public void setDisplayMsg( final String id ) {
		final String ids = getRequestParameter( id );
		if( ids != null ) { displayMsg = ids; }
	}

	/**
	 * 【TAG】検索結果がゼロ件の場合に表示するメッセージリソースIDを指定します(初期値:MSG0077[対象データはありませんでした])。
	 *
	 * @og.tag
	 * ここでは、検索結果がゼロ件の場合のみ、特別なメッセージを表示させます。
	 * 従来は、displayMsg と兼用で、『0　件検索しました』という表示でしたが、
	 * displayMsg の初期表示は、OFF になりましたので、ゼロ件の場合のみ別に表示させます。
	 * 表示させたくない場合は, notfoundMsg = "" をセットしてください。
	 * 初期値は、MSG0077[対象データはありませんでした]です。
	 *
	 * @param	id	ゼロ件メッセージID
	 */
	public void setNotfoundMsg( final String id ) {
		final String ids = getRequestParameter( id );
		if( ids != null ) { notfoundMsg = ids; }
	}

	/**
	 * 【TAG】検索結果が０件のとき処理を停止するかどうか[true/false]を指定します(初期値:false[続行する])。
	 *
	 * @og.tag
	 * 初期値は、false(続行する)です。
	 *
	 * @param	cmd	０件時停止可否 [true:処理を中止する/false:続行する]
	 */
	public void setStopZero( final String cmd ) {
		stopZero = nval( getRequestParameter( cmd ),stopZero );
	}

	/**
	 * 【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
	 *        (初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @og.tag
	 * 検索結果より、DBTableModelオブジェクトを作成します。これを、下流のviewタグ等に
	 * 渡す場合に、通常は、session を利用します。その場合の登録キーです。
	 * query タグを同時に実行して、結果を求める場合、同一メモリに配置される為、
	 * この tableId 属性を利用して、メモリ空間を分けます。
	 *(初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @param	id	テーブルID (sessionに登録する時のID)
	 */
	public void setTableId( final String id ) {
		tableId = nval( getRequestParameter( id ),tableId );
	}

	/**
	 * 【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します
	 *        (初期値:USE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。
	 *
	 * @og.tag
	 * SQLインジェクション対策の一つとして、暫定的ではありますが、SQLのパラメータに
	 * 渡す文字列にシングルクォート(') を許さない設定にすれば、ある程度は防止できます。
	 * 数字タイプの引数には、 or 5=5 などのシングルクォートを使用しないコードを埋めても、
	 * 数字チェックで検出可能です。文字タイプの場合は、必ず (')をはずして、
	 * ' or 'A' like 'A のような形式になる為、(')チェックだけでも有効です。
	 * (') が含まれていたエラーにする(true)／かノーチェックか(false)を指定します。
	 * 初期値は、SystemData#USE_SQL_INJECTION_CHECK です。
	 *
	 * @param	flag	クォートチェック [true:する/それ以外:しない]
	 */
	public void setQuotCheck( final String flag ) {
		quotCheck = nval( getRequestParameter( flag ),quotCheck );
	}

	/**
	 * 【TAG】リクエスト情報の HTMLTag開始/終了文字(&gt;&lt;) 存在チェックを実施するかどうか[true/false]を設定します
	 *        (初期値:USE_XSS_CHECK[={@og.value SystemData#USE_XSS_CHECK}])。
	 *
	 * @og.tag
	 * クロスサイトスクリプティング(XSS)対策の一環としてless/greater than signについてのチェックを行います。
	 * (&gt;&lt;) が含まれていたエラーにする(true)／かノーチェックか(false)を指定します。
	 * (初期値:システム定数のUSE_XSS_CHECK[={@og.value SystemData#USE_XSS_CHECK}])
	 *
	 * @param	flag	XSSチェック [true:する/false:しない]
	 * @see		org.opengion.hayabusa.common.SystemData#USE_XSS_CHECK
	 */
	public void setXssCheck( final String flag ) {
		xssCheck = nval( getRequestParameter( flag ),xssCheck );
	}

	/**
	 * 【TAG】(通常は使いません)タグで処理される処理がメインとなるトランザクション処理かどうかを指定します(初期値:true)。
	 *
	 * @og.tag
	 * この値は、ファイルダウンロード処理に影響します。この値がtrueに指定された時にcommitされたDBTableModelが
	 * ファイルダウンロードの対象の表になります。
	 *
	 * このパラメーターは、通常、各タグにより実装され、ユーザーが指定する必要はありません。
	 * 但し、1つのJSP内でDBTableModelが複数生成される場合に、前に処理したDBTableModelについてファイルダウンロードをさせたい
	 * 場合は、後ろでDBTableModelを生成するタグで、明示的にこの値をfalseに指定することで、ファイルダウンロード処理の対象から
	 * 除外することができます。
	 *
	 * @param	flag	メイントランザクションかどうか [true:メイン/false:その他]
	 */
	public void setMainTrans( final String flag ) {
		isMainTrans = nval( getRequestParameter( flag ),isMainTrans );
	}

	/**
	 * 【TAG】 処理時間(queryTime)などの情報出力[true:有効/false:無効]を指定します(初期値:true)。
	 *
	 * @og.tag
	 * Query で、検索する場合に、処理時間(queryTime)などの情報を出力していますが、
	 * ViewForm で、CustomData などの 非HTML表示ビューを使用する場合、データとして、
	 * 紛れ込んでしまうため、出力を抑制する必要があります。
	 * true(有効)にすると、これらのHTMLが出力されます。false にすると、出力されません。
	 * 初期値は、true(有効) です。
	 *
	 * @param	useTag	情報出力の有効/無効 [true:有効/false:無効]
	 */
	public void setUseBeforeHtmlTag( final String useTag ) {
		useBeforeHtmlTag = nval( getRequestParameter( useTag ),useBeforeHtmlTag );
	}

	/**
	 * 【TAG】処理時間を表示する TimeView を表示するかどうか[true:する/false:しない]を指定します
	 *		(初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])。
	 *
	 * @og.tag
	 * true に設定すると、処理時間を表示するバーイメージが表示されます。
	 * これは、DB検索、APサーバー処理、画面表示の各処理時間をバーイメージで
	 * 表示させる機能です。処理時間の目安になります。
	 * (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])。
	 *
	 * @param	flag	処理時間を表示 [true:する/false:しない]
	 */
	public void setUseTimeView( final String flag ) {
		useTimeView = nval( getRequestParameter( flag ),useTimeView );
	}

	/**
	 * IorQuery オブジェクトに渡すパラメータをマッピングします。
	 *
	 * IorQueryParamTag クラスよりセットされます。
	 *
	 * @param	key		パラメータキー
	 * @param	value	パラメータ値
	 */
	protected void addParam( final String key, final String value ) {
		mapParam.put( key, value );
	}

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

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return	このクラスの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION		)
				.println( "urlStr"			,urlStr			)
				.println( "timeout"			,timeout		)
				.println( "keys"			,StringUtil.array2csv( keys ) )
				.println( "vals"			,StringUtil.array2csv( vals ) )
				.println( "authURL"			,authURL		)
				.println( "authUserPass"	,authUserPass	)
				.println( "companyId"		,companyId		)
				.println( "appliName"		,appliName		)
				.println( "callMethod"		,callMethod		)
				.println( "display"			,display		)
				.println( "postData"		,postData		)
				.println( "saveFile"		,saveFile		)
				.println( "loadFile"		,loadFile		)
				.println( "tableId"			,tableId		)
				.println( "Other..."		,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}
