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

import org.opengion.hayabusa.db.Query;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.db.DBUserArg;
import org.opengion.hayabusa.db.DBSysArg;
import org.opengion.hayabusa.db.DBTableModelUtil;
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBColumnConfig;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.fukurou.util.LogWriter;
import org.opengion.hayabusa.resource.ResourceManager;
import org.opengion.hayabusa.resource.LabelData;
import org.opengion.fukurou.db.ConnectionFactory;
import org.opengion.fukurou.util.ApplicationInfo;
import org.opengion.fukurou.util.Closer ;
import org.opengion.fukurou.util.ErrorMessage;

import java.util.MissingResourceException;
import java.util.Locale ;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.sql.Clob;
import java.io.Reader;
import java.io.IOException;

/**
 * Query インターフェースを継承した Query の実装クラスです。
 * クエリークラスにステートメントを与えて execute()することにより内部に DBTableModel を
 * 作成します。
 * このクラスは、Abstract クラスのため、実装は個々のサブクラスで行います。
 * 唯一実装する必要があるのは, execute() メソッドだけです。
 *
 * @og.group ＤＢ検索
 * @og.group ＤＢ登録
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
class AbstractQuery implements Query {
	private Connection		connection = null ;
	private int 			rtnCode    = ErrorMessage.OK;
	private ErrorMessage	errMessage = null;
	private ResourceManager resource   = null;
	private ApplicationInfo appInfo    = null;	// 3.8.7.0 (2006/12/15)

	private DBTableModel table		   = null;
	private String		 connID 	   = null;
	private String		 stmtString    = null;
	private int 		 executeCount  = -1 ;
	private int 		 skipRowCount  = 0 ;
	private int 		 maxRowCount   = HybsSystem.sysInt( "DB_MAX_ROW_COUNT" ) ;
	private boolean		 updateFlag    = true ;

	private static final int DB_RETRY_COUNT		= HybsSystem.sysInt( "DB_RETRY_COUNT" ) ;
	private static final int DB_RETRY_TIME		= HybsSystem.sysInt( "DB_RETRY_TIME" ) ;
	protected static final int DB_MAX_QUERY_TIMEOUT	= HybsSystem.sysInt( "DB_MAX_QUERY_TIMEOUT" ) ;

	// 3.5.2.0 (2003/10/20) 内部オブジェクトタイプ名を システムパラメータ で定義します。
	public static final String ARG_ARRAY		= "ARG_ARRAY" ;
	public static final String SYSARG_ARRAY		= "SYSARG_ARRAY" ;
	public static final String ERR_MSG 			= "ERR_MSG" ;
	public static final String ERR_MSG_ARRAY	= "ERR_MSG_ARRAY" ;

	/**
	 * Query オブジェクトを初期化します。
	 * これは、QueryFactory のプールから取り出すときに（または戻すとき）に
	 * 初期化するのに使用します。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfo オブジェクトを設定
	 *
	 */
	public void init() {
		rtnCode		  = ErrorMessage.OK;
		errMessage	  = null;
		resource	  = null;
		appInfo		  = null;	// 3.8.7.0 (2006/12/15)
		table		  = null;
		connID		  = null;
		stmtString	  = null;
		executeCount  = -1 ;
		skipRowCount  = 0 ;
		maxRowCount   = HybsSystem.sysInt( "DB_MAX_ROW_COUNT" ) ;
		updateFlag	  = true ;
		close();
	}

	/**
	 * ステートメント文字列をセットします。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 *
	 * @param	stmt String
	 *
	 */
	public void setStatement( final String stmt ) {
		this.stmtString = stmt.trim();
	}

	/**
	 * ステートメント文字列を取り出します。
	 *
	 * @return	 stmtString String
	 *
	 */
	public String getStatement() {
		return stmtString;
	}

	/**
	 * クエリーを実行します。
	 * 実行方法等は各サブクラスの実装に依存します。
	 * セットされているステートメント文字列とそのタイプが合っていない場合は,
	 * エラーになります。
	 * 実行結果は、DBTableModel にセットされます。
	 * 実行結果の件数は #getExecuteCount() で取得できます。
	 * ※ このクラスでは実装されていません。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 *
	 */
	public void execute() {
		String errMsg = "このクラスでは実装されていません。execute()";
		throw new UnsupportedOperationException( errMsg );
	}

	/**
	 * 引数配列付のクエリーを実行します。
	 * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。
	 * これは、PreparedQuery で使用する引数を配列でセットするものです。
	 * select * from emp where deptno = ? and job = ? などの PreparedQuery や
	 * { call xxxx( ?,?,? ) } などの CallableStatement の ? 部分の引数を
	 * 順番にセットしていきます。
	 * ※ このクラスでは実装されていません。
	 *
	 * @param	args オブジェクトの引数配列
	 */
	public void execute( final String[] args ) {
		String errMsg = "このクラスでは実装されていません。execute( String[] )";
		throw new UnsupportedOperationException( errMsg );
	}

	/**
	 * 引数配列付のクエリーを実行します。
	 * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。
	 * これは、PreparedQuery で使用する引数を配列でセットするものです。
	 * select * from emp where deptno = ? and job = ? などの PreparedQuery の
	 * ? 部分の引数を
	 * 順番にセットしていきます。
	 * ※ このクラスでは実装されていません。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 新規追加
	 *
	 * @param   keys オブジェクトのキー配列
	 * @param   args オブジェクトの引数配列
	 */
	public void execute( final String[] keys, final String[] args ) {
		String errMsg = "このクラスでは実装されていません。execute( String[],String[] )";
		throw new UnsupportedOperationException( errMsg );
	}

	/**
	 * 引数配列付のクエリーを実行します。
	 * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。
	 * これは、PreparedQuery で使用する引数を配列でセットするものです。
	 * select * from emp where deptno = ? and job = ? などの PreparedQuery の
	 * ? 部分の引数を
	 * 順番にセットしていきます。
	 * ※ このクラスでは実装されていません。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 引数をすべて受け取って実行するメソッドを標準メソッドとして追加
	 *
	 * @param names       String
	 * @param dbArrayType String
	 * @param sysArg    DBSysArg[]
	 * @param userArg   DBUserArg[]
	 */
	public void execute( final String names,final String dbArrayType,
						final DBSysArg[] sysArg,final DBUserArg[] userArg ) {
		String errMsg = "このクラスでは実装されていません。execute( String,String,DBSysArg[],DBUserArg[] )";
		throw new UnsupportedOperationException( errMsg );
	}

	/**
	 * 引数配列付のクエリーを実行します。
	 * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。
	 * これは、PreparedQuery で使用する引数を配列でセットするものです。
	 * select * from emp where deptno = ? and job = ? などの PreparedQuery の
	 * [カラム名] 部分の引数を、DBTableModelから順番にセットしていきます。
	 * ※ このクラスでは実装されていません。
	 *
	 * @param   rowNo 選択された行番号配列(登録する対象行)
	 * @param   table DBTableModel オブジェクト(登録する元データ)
	 */
	public void execute( final int[] rowNo, final DBTableModel table ) {
		String errMsg = "このクラスでは実装されていません。execute( final int[] rowNo, final DBTableModel table )";
		throw new UnsupportedOperationException( errMsg );
	}

	/**
	 * コミットを行います。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 * @og.rev 3.8.0.8 (2005/10/03) エラーメッセージの出力順をメッセージ＋Queryに変更します。
	 *
	 */
	public void commit() {
		if( connection == null ) { return; }
		try {
			connection.commit();
		}
		catch( SQLException ex ) {
			realClose();
			String errMsg = "コミットすることが出来ませんでした。" + HybsSystem.CR
						+ ex.getMessage() + ":" + ex.getSQLState()
						+ HybsSystem.CR + getStatement() + HybsSystem.CR ;
			throw new HybsSystemException( errMsg,ex );		// 3.5.5.4 (2004/04/15) 引数の並び順変更
		}
	}

	/**
	 * ロールバックを行います。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 * @og.rev 3.8.0.8 (2005/10/03) エラーメッセージの出力順をメッセージ＋Queryに変更します。
	 *
	 */
	public void rollback() {
		if( connection == null ) { return; }
		try {
			connection.rollback();
		}
		catch( SQLException ex ) {
			realClose();
			String errMsg = "ロールバックすることが出来ません。" + HybsSystem.CR
						 + ex.getMessage() + ":" + ex.getSQLState()
						 + HybsSystem.CR + getStatement() + HybsSystem.CR ;
			throw new HybsSystemException( errMsg,ex );		// 3.5.5.4 (2004/04/15) 引数の並び順変更
		}
	}

	/**
	 * 使用した Statementオブジェクトをクロースし、Connection オブジェクトを
	 * プールに返します。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 * @og.rev 3.6.0.4 (2004/10/14) SQLWarning の取得(getWarning)をコメントアウトします。
	 *
	 */
	public void close() {
		if( connection != null ) {
			try {
				// 4.0.0 (2005/01/31)
				if( rtnCode > ErrorMessage.NG ) {
					realClose();
				}
			}
			finally {
				ConnectionFactory.close( connection,connID );	// 4.0.0 (2005/01/31)
				connection = null;
			}
		}
	}

	/**
	 * Connection オブジェクトを実際にクローズ（破棄）します。
	 * プールからも削除します。
	 * 実行時エラー等が発生したときに、このメソッドを呼び出します。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 *
	 */
	public void realClose() {
		if( connection != null ) { ConnectionFactory.remove( connection,connID ); }
		connection = null;
	}

	/**
	 * クエリーの実行結果件数をセットします。
	 * 初期値は -1 です。（クエリーが失敗した場合や,CallableStatement の呼び出し等で
	 * 実行件数が明確でない場合の戻り値）。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 *
	 * @param	executeCount int
	 */
	protected void setExecuteCount( final int executeCount ) {
		this.executeCount = executeCount;
	}

	/**
	 * クエリーの実行結果を返します。
	 * クエリーが失敗した場合や,CallableStatement の呼び出し等で実行件数が明確でない
	 * 場合は, -1 が返されます。
	 *
	 * @return	executeCount
	 */
	public int getExecuteCount() {
		return executeCount;
	}

	/**
	 * DBTableModel をセットします。
	 * なお、検索系実行前に setDBTableModel() でテーブルをセットしていたとしても
	 * そのオブジェクトは破棄されて、新しい DBTableModel が生成されます。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 *
	 * @param	table DBTableModel
	 */
	protected void setDBTableModel( final DBTableModel table ) {
		this.table = table;
	}

	/**
	 * 実行結果の DBTableModel を返します。
	 *
	 * @return	DBTableModel
	 */
	public DBTableModel getDBTableModel() {
		return table;
	}

	/**
	 * データベースの最大検索件数を返します。
	 * データベース自体の検索は,指定されたSQLの全件を検索しますが,
	 * DBTableModelのデータとして登録する最大件数をこの値に設定します。
	 * サーバーのメモリ資源と応答時間の確保の為です。
	 *
	 * @return	最大検索件数
	 */
	public int getMaxRowCount() {
		return maxRowCount;
	}

	/**
	 * データベースの最大検索件数をセットします。
	 * データベース自体の検索は,指定されたSQLの全件を検索しますが,
	 * DBTableModelのデータとして登録する最大件数をこの値に設定します。
	 * サーバーのメモリ資源と応答時間の確保の為です。
	 * ゼロ、または、負の値を設定すると、無制限(Integer.MAX_VALUE)になります。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 * @og.rev 4.0.0 (2005/08/31) ゼロ、または、負の値は、無制限(Integer.MAX_VALUE)にする。
	 *
	 * @param	maxRowCount int
	 */
	public void setMaxRowCount( final int maxRowCount ) {
		this.maxRowCount = ( maxRowCount > 0 ) ? maxRowCount : Integer.MAX_VALUE ;
	}

	/**
	 * データベースの検索スキップ件数を返します。
	 * データベース自体の検索は,指定されたSQLの全件を検索しますが,
	 * DBTableModelのデータとしては、スキップ件数分は登録されません。
	 * サーバーのメモリ資源と応答時間の確保の為です。
	 *
	 * @return	最大検索件数
	 */
	public int getSkipRowCount() {
		return skipRowCount;
	}

	/**
	 * データベースの検索スキップ件数をセットします。
	 * データベース自体の検索は,指定されたSQLの全件を検索しますが,
	 * DBTableModelのデータとしては、スキップ件数分は登録されません。
	 * サーバーのメモリ資源と応答時間の確保の為です。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 *
	 * @param	skipRowCount int
	 */
	public void setSkipRowCount( final int skipRowCount ) {
		this.skipRowCount = skipRowCount;
	}

	/**
	 * データベースの接続先IDをセットします。
	 * システムパラメータ ファイルに定義してある データベース識別IDによって、
	 * 接続先を切り替えます。
	 * この接続先IDを元に,Connection オブジェクトを作成します。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 *
	 * @param	connID String
	 */
	public void setConnectionID( final String connID ) {
		close();
		this.connID = connID;
	}

	/**
	 * アップデートフラグをセットします。
	 * これは、Query で更新処理の SQL 文を実行したときにセットされます。
	 * 更新処理が実行：true / 検索処理のみ：false をセットします。
	 * このメソッドを呼び出さない場合は、デフォルト：true	です。
	 *
	 * @og.rev 2.1.2.3 (2002/12/02) データベース更新時に、更新フラグをセットするように変更
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 *
	 * @param	up アップデートされたかどうか（ 更新処理：true / 検索処理：false ）
	 */
	protected void setUpdateFlag( final boolean up ) {
		updateFlag = up;
	}

	/**
	 * アップデートフラグを取得します。
	 * これは、Query で更新処理の SQL 文を実行したときに true にセットされます。
	 * 更新処理が実行：true / 検索処理のみ：false を取得できます。
	 *
	 * @og.rev 2.1.2.3 (2002/12/02) データベース更新時に、更新フラグをセットするように変更
	 * @og.rev 4.0.0 (2007/07/20) メソッド名変更( getUpdateFlag() ⇒ isUpdate() )
	 *
	 * @return	 アップデートされたかどうか（ 更新処理：true / 検索処理：false ）
	 */
	public boolean isUpdate() {
		return updateFlag ;
	}

	/**
	 * リソースマネージャーをセットします。
	 * これは、言語（ロケール）に応じた DBColumn をあらかじめ設定しておく為に
	 * 必要です。
	 * リソースマネージャーが設定されていない、または、所定のキーの DBColumn が
	 * リソースに存在しない場合は、内部で DBColumn オブジェクトを作成します。
	 *
	 * @og.rev 4.0.0 (2005/01/31) lang ⇒ ResourceManager へ変更
	 *
	 * @param	resource リソースマネージャー
	 */
	public void setResourceManager( final ResourceManager resource ) {
		this.resource = resource;
	}

	/**
	 * アクセスログ取得の為,ApplicationInfo オブジェクトを設定します。 <br />
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) 新規追加
	 *
	 * @param   appInfo ApplicationInfo
	 */
	public void setApplicationInfo( final ApplicationInfo appInfo ) {
		this.appInfo = appInfo;
	}

	/**
	 * 内部のリソースを元に言語コード を返します。
	 * 内部にリソースが登録されていない場合は, null を返します。
	 *
	 * @return	 言語コード
	 */
	public String getLang() {
		String lang = null;
		if( resource != null ) { lang = resource.getLang(); }
		return lang;
	}

	/**
	 * エラーコード を取得します。
	 * エラーコード は、ErrorMessage クラスで規定されているコードです。
	 *
	 * @return   エラーコード
	 */
	public int getErrorCode() { return rtnCode; }

	/**
	 * エラーコード をセットします。
	 * エラーコード は、ErrorMessage クラスで規定されているコードです。
	 *
	 * @param   cd エラーコード
	 */
	protected void setErrorCode( final int cd ) { rtnCode = cd; }

	/**
	 * エラーメッセージオブジェクト を取得します。
	 *
	 * @return   エラーメッセージオブジェクト
	 */
	public ErrorMessage getErrorMessage() { return errMessage; }

	/**
	 * エラーメッセージオブジェクト をセットします。
	 *
	 * @param   em エラーメッセージオブジェクト
	 */
	protected void setErrorMessage( final ErrorMessage em ) { errMessage = em; }

	//////////////////////////////////////////////////////////////////////////
	//
	//	 継承時にサブクラスから使用するメソッド類（ protected ）
	//
	//////////////////////////////////////////////////////////////////////////

	/**
	 * ResultSet を DBTableModelに割り当てます。
	 *
	 * 毎回,検索毎に,DBTableModel にコピーするイメージです。
	 * ResulSet 以外のオブジェクトから，DBTableModelを作成する場合は,
	 * このメソッドをオーバーライドします。
	 *
	 * このメソッドは， execute からのみ,呼び出されます。
	 * それ以外からは呼出し出来ません。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 * @og.rev 3.3.3.3 (2003/08/06) カラムのラベル名を、大文字に変換する。
	 * @og.rev 3.8.5.0 (2006/03/02) CLOB カラムかどうかを判定しCLOBの場合は、Clob オブジェクトから文字列を取り出します。
	 * @og.rev 3.8.8.8 (2007/05/11) ROWID対応(小数点対応 "0.3" が ".3" と表示される対策)
	 * @og.rev 4.0.0 (2006/01/31) CLOB カラムかどうかを判定しCLOBの場合は、ストリームから値を取り出します。
	 *
	 * @param	resultSet ResultSet
	 */
	protected void createTableModel( final ResultSet resultSet ) {
		try {
			ResultSetMetaData metaData	= resultSet.getMetaData();

			int numberOfColumns =  metaData.getColumnCount();

			table = DBTableModelUtil.newDBTable() ;
			table.init( numberOfColumns );

			// 項目名，項目タイプ，項目サイズ，書込みフラグを設定する。
			DBColumn[] dbColumn = new DBColumn[numberOfColumns];

			// 3.8.5.0 (2006/03/02) CLOB/ROWID などのカラムかどうかを判定します。
			boolean   isOther = false;
			int[] types  = new int[numberOfColumns];

			for( int column=0; column<numberOfColumns; column++ ) {
				String	name	 = (metaData.getColumnLabel(column+1)).toUpperCase(Locale.JAPAN) ;
				dbColumn[column] = resource.getDBColumn( name );
				if( dbColumn[column] == null ) {
					LabelData labelData  = resource.getLabelData( name );
					dbColumn[column] = makeDBColumn( name,labelData,metaData,column );
				}
				table.setDBColumn( column,dbColumn[column] );

				// 3.8.5.0 (2006/03/02) CLOB カラムかどうかを判定します。
				types[column] = metaData.getColumnType(column+1);
				if( types[column] == Types.CLOB || types[column] == Types.ROWID ) {	// JDK 6.0以降 ROWID
					isOther = true;
				}
			}

			// データ部の設定
			int numberOfRows = 0;
			while( numberOfRows < getSkipRowCount() && resultSet.next() ) {
				// 注意 resultSet.next() を先に判定すると必ず１件読み飛ばしてしまう。
				numberOfRows ++ ;
			}
	//		resultSet.absolute( getSkipRowCount() );
			numberOfRows = 0;

			// 3.8.5.1 (2006/05/08) 行列のループなので、 CLOB 使用可否でループを分ける。
			if( isOther ) {
				while( numberOfRows < maxRowCount && resultSet.next() ) {
					numberOfRows ++ ;
					String[] columnValues = new String[numberOfColumns];
					for( int column=0; column<numberOfColumns; column++ ) {
						if( types[column] == Types.CLOB ) {
							Object obj = resultSet.getObject(column+1);
							columnValues[column] = getClobData( (Clob)obj ) ;
						}
						else if( types[column] == Types.ROWID ) {
							String obj = resultSet.getString(column+1);
							if( obj == null ) {
								columnValues[column] = "";
							}
							else {
								columnValues[column] = obj;
							}
						}
						else {
							Object obj = resultSet.getObject(column+1);
							if( obj == null ) {
								columnValues[column] = "";
							}
							else {
								columnValues[column] = String.valueOf( obj );
							}
						}
					}
					table.addColumnValues( columnValues );
				}
			}
			else {
				while( numberOfRows < maxRowCount && resultSet.next() ) {
					numberOfRows ++ ;
					String[] columnValues = new String[numberOfColumns];
					for( int column=0; column<numberOfColumns; column++ ) {
						Object obj = resultSet.getObject(column+1);
				//		String obj = resultSet.getString(column+1);
						if( obj == null ) {
							columnValues[column] = "";
						}
						else {
							columnValues[column] = String.valueOf( obj );
				//			columnValues[column] = obj;
						}
					}
					table.addColumnValues( columnValues );
				}
			}
			// 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
			if( numberOfRows >= maxRowCount && resultSet.next() ) {
				table.setOverflow( true );
			}
			setExecuteCount( numberOfRows );
		}
		catch( SQLException ex ) {
			realClose();
			String errMsg = "テーブルモデルを作成できませんでした。";
			throw new HybsSystemException( errMsg,ex );		// 3.5.5.4 (2004/04/15) 引数の並び順変更
		}
	}

	/**
	 * ResultSetMetaData から、DBColumn オブジェクトを作成します。
	 *
	 * DBColumn オブジェクト がリソースファイルに定義されていない場合に、
	 * データベースの検索結果のメタデータを利用して、DBColumn オブジェクトを
	 * 作成します。
	 *
	 * @og.rev 3.4.0.0 (2003/09/01) 表示パラメータ、編集パラメータ、文字パラメータの追加。
	 * @og.rev 3.4.0.2 (2003/09/05) DBType のデフォルト値を、'X' から 'XK' に変更します。
	 * @og.rev 3.6.0.7 (2004/11/06) DBColumn の official属性追加
	 * @og.rev 4.0.0 (2005/01/31) lang 変数を取得
	 *
	 * @param	name     String
	 * @param	labelData    LabelData
	 * @param	metaData ResultSetMetaData
	 * @param	column   int
	 * @return	DBColumn
	 */
	private DBColumn makeDBColumn( final String name,final LabelData labelData,
							final ResultSetMetaData metaData,final int column ) {
		final DBColumn dbColumn ;

		try {
			String	clsName  = type2ClassName( metaData.getColumnType(column+1) );
			int 	size	 = metaData.getColumnDisplaySize(column+1);
			if( size == 0 ) { size = 60; }
			boolean writable = metaData.isWritable(column+1);
			String	dbType	 = ( "NUMBER".equals( clsName )) ? "S9" : "XK" ;
			String	defValue = ( "NUMBER".equals( clsName )) ? "0"	: ""  ;
			DBColumnConfig config = new DBColumnConfig(
				getLang(),						// 言語
				name,							// カラム名
				labelData,						// カラムのラベルデータオブジェクト
				clsName ,						// カラムのクラスを文字列にした名称
				String.valueOf( size ) ,		// カラムの文字桁数
				String.valueOf( writable ) ,	// カラムが書き込み可能かどうか
				null ,							// データの表示用レンデラー
				null ,							// データの編集用エディター
				null ,							// メニューの項目コードデータオブジェクト
				dbType ,						// データのタイプ
				defValue,						// データのデフォルト値
				null ,							// 表示用レンデラーのパラメータ
				null ,							// 編集用エディターのパラメータ
				null ,							// データのタイプのパラメータ
				null ,							// カラムロール
				false,							// 正式なカラムオブジェクトかどうか
				null							// データベース接続先ID
			);

			dbColumn = new DBColumn( config );		// 4.0.0 (2005/01/31)

		}
		catch( SQLException ex ) {
			realClose();
			String errMsg = "DBColumn を作成できませんでした。name=[" + name + " , label=[" + labelData + "]";
			throw new HybsSystemException( errMsg,ex );		// 3.5.5.4 (2004/04/15) 引数の並び順変更
		}
		catch( RuntimeException ex2 ) {
			realClose();
			String errMsg = "予期せぬエラーが発生しました。name=[" + name + " , label=[" + labelData + "]";
			throw new HybsSystemException( errMsg,ex2 );		// 3.6.0.0 (2004/09/17)
		}

		return dbColumn;
	}

	/**
	 * ConnectionFactory.connection( String ); を利用して,Connection
	 * オブジェクトを取り出します。
	 *
	 * 取り出しに失敗（コネクションプールが一杯）の場合は,内部的に
	 * DB_RETRY_TIME だけ待機して, DB_RETRY_COUNT 回数だけ,試行します。
	 * それでも取り出せない場合は, HybsSystemException を throw します。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド（synchronized付き）を非同期に変更する。
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfo オブジェクトを設定
	 *
	 * @return	Connection
	 */
	protected Connection getConnection() {
		if( connection != null ) { return connection; }
		MissingResourceException exTemp = null;
		for( int i=0; i<DB_RETRY_COUNT; i++ ) {
			try {
				connection = ConnectionFactory.connection( connID,appInfo );
				return connection;
			}
			catch( MissingResourceException ex ) {
				try {
					exTemp = ex;
					Thread.sleep( DB_RETRY_TIME );
				}
				catch ( InterruptedException ex2) {
					LogWriter.log( "InterruptedException:" + ex2.getMessage() );
				}
			}
		}
		String errMsg = "コネクションを取り出すことが出来ませんでした。 "
						+ HybsSystem.CR
						+ "connID = [" + connID + "]" ;
		throw new HybsSystemException( errMsg,exTemp );
	}

	/**
	 * connection オブジェクトから，ワーニングデータを取り出します。
	 *
	 * ワーニングデータは，SQLWarning クラスのオブジェクトに複数件貯えられます。
	 * query 実行後に,確認しておく必要があります。
	 *
	 * このメソッドは， execute からのみ,呼び出されます。
	 * それ以外からは呼出し出来ません。
	 *
	 * @param	connection Connection
	 * @return	ワーニング ErrorMessage
	 */
	//	protected ErrorMessage getWarning( final Connection connection ) {
	//		if( connection == null ) { return null; }
	//
	//		try {
	//			ErrorMessage em = new ErrorMessage();
	//			for( SQLWarning warning = connection.getWarnings();
	//							warning != null ;
	//							warning = warning.getNextWarning() ) {
	//				em.addMessage( 0,ErrorMessage.WARNING,warning.getMessage(),warning.getSQLState() );
	//			}
	//			return em;
	//		}
	//		catch (SQLException ex) {
	//			realClose();
	//			String errMsg = "ワーニングを取り出すことが出来ませんでした。";
	//			errMsg += System.getProperty( "line.separator" );
	//			errMsg += ex.getMessage() + ":" + ex.getSQLState();
	//			throw new HybsSystemException( errMsg,ex );		// 3.5.5.4 (2004/04/15) 引数の並び順変更
	//		}
	//	}

	/**
	 * カラムのタイプを表現する文字列値を返します。
	 *
	 * この文字列を用いて、CCSファイルでタイプごとの表示方法を
	 * 指定することができます。
	 *
	 * @og.rev 2.1.1.1 (2002/11/15) その他のケースを、VARCHAR2 を返すように修正。
	 * @og.rev 4.0.0 (2006/01/31) CLOB を追加
	 *
	 * @param	type int
	 * @return	カラムのタイプを表現する文字列値
	 */
	protected String type2ClassName( final int type ) {
		final String rtn ;

		switch( type ) {
			case Types.CHAR:
			case Types.VARCHAR:
			case Types.BIT:
				rtn = "VARCHAR2"; break;
			case Types.LONGVARCHAR:			// 4.0.0 (2006/01/31)
				rtn = "LONG"; break;
			case Types.TINYINT:
			case Types.SMALLINT:
			case Types.INTEGER:
			case Types.NUMERIC:
			case Types.BIGINT:
			case Types.FLOAT:
			case Types.DOUBLE:
			case Types.REAL:
				rtn = "NUMBER"; break;
			case Types.DATE:
				rtn = "DATE"; break;
			case Types.CLOB:			// 4.0.0 (2006/01/31)
				rtn = "CLOB"; break;
			default:
				rtn = "NONE"; break;
		}

		return rtn;
	}

	/**
	 * Clob オブジェクトから文字列を取り出します。
	 *
	 * @og.rev 4.0.0 (2006/01/31) 新規作成
	 *
	 * @param	clobData Clob
	 * @return	String
	 * @throws	SQLException
	 */
	private String getClobData( final Clob clobData ) throws SQLException {
		if( clobData == null ) { return ""; }

		Reader reader = null;
		StringBuilder buf = new StringBuilder( 10000 );

		try {
			reader = clobData.getCharacterStream();
			char[] ch = new char[10000];
			int  len ;
			while( (len = reader.read( ch )) >= 0 ) {
				buf.append( ch,0,len );
			}
		}
		catch( IOException ex ) {
			String errMsg = "CLOBデータの読み込みに失敗しました。";
			throw new HybsSystemException( errMsg,ex );
		}
		finally {
			Closer.ioClose( reader );
		}
		return buf.toString();
	}

	//////////////////////////////////////////////////////////////////////////
	//
	//	 Object クラスのオーバーライド部分
	//
	//////////////////////////////////////////////////////////////////////////

	/**
	 * オブジェクトの識別子として，最後のクエリーを返します。
	 *
	 * @return	最後のクエリー
	 */
	public String toString() {
		return	"LastQuery  :[" + getStatement() + "] ";
	}
}
