/*
 * 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.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

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) に接続し、取得したデータベースを表示するタグです。
 *
 * IOr へデータ取得を要求して受取った JSON形式 の文字列を、DBTableModel にセットします。
 * IOr から取得したデータより loadFile が優先されます。
 *
 * このタグの結果（DBTableModel）は、通常の QueryTag と同様に
 * ViewFormTag で一覧表示や、WriteTableTag でファイル出力が可能です。
 *
 * 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 : 実行結果のエラーコード
 *   DB.ERR_MSG  : 実行結果のエラーメッセージ
 *
 * ※ このタグは、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}])
 *       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に登録するときのキーを指定します
 *       stopError          【TAG】処理エラーの時に処理を中止するかどうか[true/false]を設定します (初期値:true)
 *       dispError          【TAG】エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用 (初期値:true)
 *       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':'{&#064;PN}%','TANI':'{&#064;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 ;

	/** command 引数に渡す事の出来る コマンド  新規 {@value} */
	public static final String CMD_NEW		= "NEW";											// コマンド(新規)
	/** command 引数に渡す事の出来る コマンド  再検索 {@value} */
	public static final String CMD_RENEW	= "RENEW";											// コマンド(再検索)
	// String配列 から、Setに置き換えます。
	private static final Set<String> COMMAND_SET = new ArraySet<>( CMD_NEW, CMD_RENEW );		// コマンド(Set)

	/** エラーメッセージID */
	protected static final String ERR_MSG_ID	= HybsSystem.ERR_MSG_KEY;						// エラーメッセージID

	// IOr 応答結果のキー
	private   static final String IOR_HTTP_STTS	= "status";										// ステータス
	private   static final String IOR_HTTP_MSG	= "message";									// エラーメッセージ
	/** ヘッダ配列のラベル */
	protected static final String IOR_DISP_LBL	= "display_label";								// ヘッダ配列のラベル
	/** ヘッダ配列のキー */
	protected static final String IOR_DISP_KEY	= "display";									// ヘッダ配列のキー
	private   static final String IOR_SQL_MSG	= "information";								// 処理後メッセージ
	private   static final String IOR_SQL_CNT	= "insert_count";								// 実行件数

	// JSONによるフェーズ認証フォーマット
	private static final String AUTH_JSON_FMT	= "{\"userInfo\":{\"companyId\":\"%s\",\"userId\":\"%s\",\"password\":\"%s\"}}";

	/** JSONコード */
	protected final Map<String,String> mapParam = new LinkedHashMap<>();						// JSONコード
	/** テーブルモデル */
	protected transient DBTableModel table;														// テーブルモデル

	private   String	urlStr				;													// 接続するURL
	private   String	proxyHost			= HybsSystem.sys( "HTTP_PROXY_HOST" );				// プロキシホスト名
	private   int		proxyPort			= HybsSystem.sysInt( "HTTP_PROXY_PORT" );			// プロキシポート番号
	private   int		timeout				= HybsSystem.sysInt( "URL_CONNECT_TIMEOUT" );		// 接続タイムアウト時間
	private   String	authURL				;													// JSONコードで認証するURL
	private   String	authUserPass		;													// ユーザー:パスワード
	private   String	companyId			= HybsSystem.sys( "IOR_COMPANYID" );				// 企業ID
	private   String	appliName			;													// アプリ名
	private   String	callMethod			;													// 関数名
	private   boolean	display				;													// 結果の表示可否
	private   String	saveFile			;													// 保存ファイル
	/** 読取ファイル */
	protected String	loadFile			;													// 読取ファイル
	private   String	command				= CMD_NEW;											// コマンド
	private   String	displayMsg			= HybsSystem.sys( "VIEW_DISPLAY_MSG" );				// ディスプレイメッセージ
	private   String	notfoundMsg			= "MSG0077";										// 対象データはありませんでした。
	private   boolean	stopZero			;													// 処理の停止可否
	/** テーブルモデルID */
	protected String	tableId				= HybsSystem.TBL_MDL_KEY;							// テーブルモデルID
	/** エラー時の処理中止可否 */
	protected boolean	stopError			= true;												// エラー時の処理中止可否
	private   boolean	dispError			= true;												// 画面上のエラー出力可否
	/** クオートチェック */
	protected boolean	quotCheck			= HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );	// クオートチェック
	private   boolean	xssCheck			= HybsSystem.sysBool( "USE_XSS_CHECK" );			// XSSチェック
	private   boolean	useBeforeHtmlTag	= true;												// 処理時間(queryTime)などの情報出力の可否
	/** タイムバーの使用可否 */
	protected boolean	useTimeView			= HybsSystem.sysBool( "VIEW_USE_TIMEBAR" );			// タイムバーの使用可否

	private   String	user				;													// ユーザー
	private   String	pass				;													// パスワード
	/** BODY部 */
	protected String	postData			;													// BODY部
	/** 取得データ */
	protected String	rtnData				;													// 取得データ
	private   int		executeCount		;													// 実行件数
	/** 現在時刻 */
	protected long		dyStart				;													// 現在時刻
	private   boolean	isMainTrans			= true;												// DBLastSqlの処理見直し
	private   String	fileURL				= HybsSystem.sys( "FILE_URL" );						// ファイルURL

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

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

		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 );
		}

		// ユーザー:パスワード から ユーザーとパスワードを取得します。
		checkUsrPw();

		// 読取ファイル指定無し
		if( loadFile == null ) {
			// JSON形式の共通要求キーを設定します。
			setComJson();
		}
		return EVAL_BODY_BUFFERED;												// Body を評価する (extends BodyTagSupport 時)
	}

	/**
	 * 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 ; }

		int errCode = ErrorMessage.OK;											// エラーコード
		if( check( command, COMMAND_SET ) ) {
			// URLに対して応答結果を取得します。
			rtnData = outJson();

			// IOr の応答結果を特定のキーワードで分割します。
			final SplitReqJson splitJson = new SplitReqJson( rtnData );
			final String rtnStts = splitJson.getSttsJson();

			// 応答結果のHTTPステータスコードを設定します。
			errCode = getStatus( rtnStts );

			if( errCode == ErrorMessage.OK ) {
				// テーブルモデル作成します。
				table = makeDBTable( splitJson );

				// 処理後のメッセージを作成します。
				errCode = makeMessage();
			}
		}

		final int rtnCode;
		// 異常
		if( errCode >= ErrorMessage.NG ) {
			rtnCode = stopError ? SKIP_PAGE : EVAL_PAGE ;
		}
		// 正常／警告
		else {
			// 実行件数 = ゼロ 且つ stopZero = true
			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();
		mapParam.clear();														// JSONコードのクリア
		table				= null;												// テーブルモデル
		urlStr				= null;												// 接続するURL
		proxyHost			= HybsSystem.sys( "HTTP_PROXY_HOST" );				// プロキシホスト名
		proxyPort			= HybsSystem.sysInt( "HTTP_PROXY_PORT" );			// プロキシポート番号
		timeout				= HybsSystem.sysInt( "URL_CONNECT_TIMEOUT" );		// 接続タイムアウト時間
		authURL				= null;												// JSONコードで認証するURL
		authUserPass		= null;												// ユーザー:パスワード
		companyId			= HybsSystem.sys( "IOR_COMPANYID" );				// 企業ID
		appliName			= null;												// アプリ名
		callMethod			= null;												// 関数名
		display				= false;											// 結果の表示可否
		saveFile			= null;												// 保存ファイル
		loadFile			= null;												// 読取ファイル
		command				= CMD_NEW;											// コマンド
		displayMsg			= HybsSystem.sys( "VIEW_DISPLAY_MSG" );				// ディスプレイメッセージ
		notfoundMsg			= "MSG0077";										// 対象データはありませんでした。
		stopZero			= false;											// 処理の停止可否
		tableId				= HybsSystem.TBL_MDL_KEY;							// テーブルモデル
		stopError			= true;												// エラー時の処理中止可否
		dispError			= true;												// 画面上のエラー出力可否
		quotCheck			= HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );	// クオートチェック
		xssCheck			= HybsSystem.sysBool( "USE_XSS_CHECK" );			// XSSチェック
		useBeforeHtmlTag	= true;												// 処理時間(queryTime)などの情報出力の可否
		useTimeView			= HybsSystem.sysBool( "VIEW_USE_TIMEBAR" );			// タイムバーの使用可否

		user				= null;												// ユーザー
		pass				= null;												// パスワード
		postData			= null;												// BODY部
		rtnData				= null;												// 取得データ
		executeCount		= 0;												// 実行件数
		dyStart				= 0;												// 現在時刻
		isMainTrans			= true;												// DBLastSqlの処理見直し
		fileURL				= HybsSystem.sys( "FILE_URL" );						// ファイルURL
	}

	/**
	 * IorQuery オブジェクトに渡すパラメータをマッピングします。
	 *
	 * IorQueryParamTag クラスよりセットされます。
	 * 但し、以下のキーに対して IorQueryParamTag では指定できません。
	 * company_id、user_id、session_id、report_name、method
	 *
	 * @param	key	パラメータキー
	 * @param	val	パラメータ値
	 */
	protected void addParam( final String key, final String val ) {
		if( mapParam.containsKey( key ) ){
			final String errMsg = key + "のキーが重複しています。";
			throw new HybsSystemException( errMsg );
		}
		else {
			mapParam.put( key, val );
		}
	}

	/**
	 * ユーザー:パスワード から ユーザーとパスワードを分割します。
	 */
	protected void checkUsrPw() {
		if( authUserPass.contains(":") ) {
			// ユーザー:パスワードを分割します。
			final String[] prm = StringUtil.csv2Array( authUserPass, ':' ,2 );
			user = prm[0];														// ユーザー
			pass = prm[1];														// パスワード
		}
		else {
			final String errMsg = "ユーザー:パスワード の形式で記述してください。";
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * JSON形式の共通要求キーを設定します。
	 * ・company_id   : 企業ID
	 * ・user_id      : ユーザーID
	 * ・session_id   : セッションID
	 * ・report_name  : アプリ名
	 * ・method       : メソッド
	 * 例：{"company_id":"XXXXX","user_id":"admin","session_id":"$session_id$" …}
	 */
	protected void setComJson() {
		// 企業ID
		if( companyId != null ) { mapParam.put( "company_id", companyId ); }
		// ユーザーID
		if( !user.isEmpty() ) { mapParam.put( "user_id", user ); }
		// セッションID
		mapParam.put( "session_id", "$session_id$" );
		// アプリ名
		if( appliName != null ) { mapParam.put( "report_name", appliName ); }
		// メソッド
		if( callMethod != null ) { mapParam.put( "method", callMethod ); }
	}

	/**
	 * loadFile 指定がある場合は、ファイルからデータを読取ります。
	 * loadFile 指定がない場合は、URLに対して応答結果を取得します。
	 *
	 * @return	JSON形式の文字列
	 * @og.rtnNotNull
	 */
	protected String outJson() {
		final String str;

		// 読取ファイル指定無し
		if( loadFile == null ) {
			if( postData == null || postData.isEmpty() ){
				postData = JSONScan.map2Json( mapParam );
			}
			// URLに対して応答結果を取得します。
			str = retResponse();
		}
		// 読取ファイル指定有り
		else {
			final StringBuilder buf = new StringBuilder(BUFFER_MIDDLE);
			try {
				for (final String text : Files.readAllLines( Paths.get(loadFile), StandardCharsets.UTF_8 )) {
					buf.append( text )
						.append( CR );
				}
				str = buf.toString();
			}
			catch( final IOException ex ) {
				final String errMsg = "loadFile 処理中でエラーが発生しました。"	+ CR
							+ "\t " + ex.getMessage()							+ CR ;
				throw new HybsSystemException( errMsg, ex );
			}
		}
		return str;
	}

	/**
	 * URLに対して応答結果を取得します。
	 *
	 * @return	URL接続先のデータ
	 * @og.rtnNotNull
	 */
	protected String retResponse() {
		HttpConnect conn = null;
		Writer outWriter = null;
		String getData = null;
		try {
			conn = connect();

			// URL接続先のデータを取得します。
			getData = 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文字列から元の文字列に変換します。
			getData = StringUtil.convertToOiginal( getData );

			// 出力先が存在する場合
			if( outWriter != null ) {
				outWriter.write( getData );
			}
		}
		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 );
		}
		return getData;
	}

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

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

	/**
	 * IOr の要求結果から、HTTPステータスコードを設定します。
	 *
	 * @param	strJson	Json形式の文字列
	 * @return	エラーコード
	 * @og.rtnNotNull
	 */
	protected int getStatus( final String strJson ) {
		int errCode = ErrorMessage.OK;											// エラーコード
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );			// バッファ

		// ---------------------------------------------------------------------
		// HTTPステータスコード (先頭から"data"までの文字列)
		// 例：{"status": 200, "message": "OK", "sessionInfo": "51b16"
		// ---------------------------------------------------------------------
		// キーと値をマッピングします。
		final Map<String,String> mapStts = JSONScan.json2Map( strJson );
		final String stts = mapStts.get( IOR_HTTP_STTS );						// ステータス

		// ステータスが'200'(正常)である
		if( stts.equals( String.valueOf( 200 ) ) ) {
			errCode = ErrorMessage.OK;											// 正常
			displayMsg = "　件の" + mapStts.get( IOR_SQL_MSG );					// 処理メッセージ
			executeCount = nval( mapStts.get( IOR_SQL_CNT ), -1 );				// 実行件数

			// 実行結果がゼロ件の場合に画面上に表示します。
			if( CMD_NEW.equals( command ) && executeCount == 0
				&& notfoundMsg != null && notfoundMsg.length() > 0 ) {
					buf.append( getResource().getLabel( notfoundMsg ) )
						.append( BR );
			}
		}
		// ステータスが'200'(正常)ではない
		else {
			errCode = ErrorMessage.NG;												// エラーコード
			String msg = mapStts.get( IOR_HTTP_MSG );								// エラーメッセージ
			// HTTPエラーではない
			if( "NG".equalsIgnoreCase(msg) ) { msg = mapStts.get( IOR_SQL_MSG ); }	// 処理メッセージ

			final ErrorMessage errMessage = new ErrorMessage( "iorQueryTag Error!" );
			errMessage.addMessage( 0, errCode, 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 );
			}
		}

		final String label = buf.toString();
		// dispErrorで表示をコントロール
		if( dispError ) { jspPrint( label ); }

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

		return errCode;
	}

	/**
	 * IOr の 要求結果から、値を取り出し、DBTableModel を作成します。
	 *
	 * @param	strJson	Json形式の文字列
	 * @return	テーブルモデル
	 * @og.rtnNotNull
	 */
	private DBTableModel makeDBTable( final SplitReqJson strJson ) {
		final DBTableModel table = DBTableModelUtil.newDBTable();

		// ---------------------------------------------------------------------
		// テーブルモデルの列 ("headers"から"rows"までの文字列)
		// 例："headers": [{"display_label": "品目番号", "display": "PN"}, … ]
		// ---------------------------------------------------------------------
		final String rtnClm = strJson.getClmJson();

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

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

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

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

		// ---------------------------------------------------------------------
		// テーブルモデルの行 ("cols"から最後の文字列)
		// 例："rows": [{"cols": [1, "GEN", "20211130", 32.4, "kg"]}, … ]}}
		// ---------------------------------------------------------------------
		final String rtnRow = strJson.getRowJson();

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

	/**
	 * 処理後のメッセージを作成します。
	 *
	 * @return	エラーコード
	 * @og.rtnNotNull
	 */
	protected int makeMessage() {
		int errCode = ErrorMessage.OK;												// エラーコード
		final ErrorMessage errMessage = new ErrorMessage( "iorQueryTag Error!" );	// エラーメッセージ
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );				// バッファ

		// 実行結果を画面上に表示します。
		if( CMD_NEW.equals( command ) && executeCount > 0
			&& displayMsg != null && displayMsg.length() > 0 ) {
				buf.append( executeCount )
					.append( getResource().getLabel( displayMsg ) )
					.append( BR );
		}

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

		// 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 );
		}
		final String label = buf.toString();
		// dispErrorで表示をコントロール
		if( dispError ) { jspPrint( label ); }

		// 検索結果を、"DB.ERR_CODE" キーでリクエストにセットします。
		setRequestAttribute( "DB.ERR_CODE", String.valueOf( errCode ) );
		// エラーメッセージを、"DB.ERR_MSG" キーでリクエストにセットします。
		setRequestAttribute( "DB.ERR_MSG", label );

		return errCode;
	}

	/**
	 * 【TAG】アクセスする接続先URLを指定します。
	 *
	 * @og.tag
	 * 接続するURLを指定します。(例：http:// ･･････)
	 *
	 * @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】JSONコードで認証するURLを指定します。
	 *
	 * @og.tag
	 * JSONコードで認証するURLを指定します。
	 *
	 * @param	url	JSONコードで認証するURL
	 */
	public void setAuthURL( final String url ) {
		authURL = nval( getRequestParameter( url ), authURL );
	}

	/**
	 * 【TAG】Basic認証を使用して接続する場合のユーザー:パスワードを指定します。
	 *
	 * @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 を併用することはできません。
	 *
	 * @param	flag	結果表示 [true:する/false:しない]
	 * @see		#setSaveFile( String )
	 */
	public void setDisplay( final String flag ) {
		display = nval( getRequestParameter( flag ),display );

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

	/**
	 * 【TAG】接続の結果をファイルに保存します。
	 *
	 * @og.tag
	 * 接続先のデータを受け取って、ファイルに保存します。
	 * display="true" と、saveFile を併用することはできません。
	 * loadFile が指定されていない時のみ処理を行います。
	 *
	 * @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
	 * 主にデバッグ用として使われます。
	 *
	 * @param	file	検索するファイル
	 */
	public void setLoadFile( final String file ) {
		loadFile = nval( getRequestParameter( file ),loadFile );
		if( loadFile != null ) {
			loadFile = HybsSystem.url2dir( StringUtil.urlAppend( fileURL,loadFile ) );
		}
	}

	/**
	 * 【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	flag	０件時停止可否 [true:処理を中止する/false:続行する]
	 */
	public void setStopZero( final String flag ) {
		stopZero = nval( getRequestParameter( flag ),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]を設定します(初期値:true)。
	 *
	 * @og.tag
	 * false(中止しない)に設定する場合、後続処理では、{&#064;DB.ERR_CODE}の値により、
	 * IOr の異常/正常終了によって分岐処理は可能となります。
	 * 初期値は、true(中止する)です。
	 *
	 * @param	flag	エラー時処理中止 [true:中止する/false:中止しない]
	 */
	public void setStopError( final String flag ) {
		stopError = nval( getRequestParameter( flag ),stopError );
	}

	/**
	 * 【TAG】PLSQL/SQL処理エラーの時にエラーを画面表示するか[true/false]を設定します(初期値:true)。
	 *
	 * @og.tag
	 * false(表示しない)に設定する場合、後続処理では、{&#064;DB.ERR_MSG}の値により、
	 * 本来表示されるはずだったメッセージを取得可能です。
	 * stopErrorと併用して、JSON形式でエラーを返す場合等に利用します。
	 * 初期値は、true(表示する)です。
	 * ※false指定の場合は件数や、overFlowメッセージ等も表示されなくなります。
	 *
	 * @param	flag	[true:表示する/false:表示しない]
	 */
	public void setDispError( final String flag ) {
		dispError = nval( getRequestParameter( flag ),dispError );
	}

	/**
	 * 【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 );
	}

	/**
	 * IOr の応答結果を特定のキーワードで分割します。(内部クラス)
	 *
	 * フォーマット形式のチェック と データの切り出しを行います。
	 * JSONフォーマットの例：{"status": … "data": {"headers": [ … ], "rows": [ {cols:[ … ]} ]}}
	 */
	private static final class SplitReqJson {
		private final String	strJson;										// JSON形式の文字列
		private int				endPos;											// 終了位置

		/**
		 * コンストラクター
		 *
		 * @param	strJson	JSON形式の文字列
		 */
		public SplitReqJson( final String str ) {
			strJson		= str;													// JSON形式の文字列
			endPos		= strJson.indexOf( "\"data\"" );						// 終了位置
		}

		/**
		 * HTTPステータス(先頭から "data"まで) の文字列を返します。
		 *
		 * @return	JSON形式の文字列
		 */
		public String getSttsJson() {
			if( endPos >= 0 ) {
				return strJson.substring( 0, endPos );
			} else {
				final String errMsg = "data キーが存在しません。";
				throw new HybsSystemException( errMsg );
			}
		}

		/**
		 * 列データ("headers"から "rows"まで) の文字列を返します。
		 *
		 * @return	JSON形式の文字列
		 */
		public String getClmJson() {
			final int startPos	= strJson.indexOf( "\"headers\"", endPos );		// 開始位置
			endPos	= strJson.indexOf( "\"rows\"", startPos );					// 終了位置
			if( startPos >= 0 && endPos >= 0) {
				return strJson.substring( startPos, endPos );
			} else {
				final String errMsg = "headers、rows キーのいずれかが存在しません。";
				throw new HybsSystemException( errMsg );
			}
		}

		/**
		 * 行データ("cols"から 最後まで) の文字列を返します。
		 *
		 * @return	JSON形式の文字列
		 */
		public String getRowJson() {
			final int startPos	= strJson.indexOf( "\"cols\"", endPos );		// 開始位置
			if( startPos >= 0 ) {
				return strJson.substring( startPos );
			} else {
				final String errMsg = "cols キーが存在しません。";
				throw new HybsSystemException( errMsg );
			}
		}
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return	このクラスの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION		)
				.println( "urlStr"			,urlStr			)
				.println( "timeout"			,timeout		)
				.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() ;
	}
}
