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

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.opengion.fukurou.system.HybsConst ;				// 6.1.0.0 (2014/12/26)
import org.opengion.fukurou.system.ThrowUtil;				// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.resource.ResourceManager;

/**
 * Query インターフェースを継承した Query の実装クラスです。
 * クエリークラスにステートメントを与えて execute()することにより内部に DBTableModel を
 * 作成します。
 * このクラスは、Abstract クラスのため、実装は個々のサブクラスで行います。
 * 唯一実装する必要があるのは, execute() メソッドだけです。
 *
 * @og.group ＤＢ検索
 * @og.group ＤＢ登録
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class AbstractQuery implements Query {
	/** システムの改行コードを設定します。*/
	protected static final String CR		 = HybsConst.CR;			// 6.1.0.0 (2014/12/26) refactoring
	/** StringBilderなどの初期値を設定します。	{@value} */
	protected static final int BUFFER_MIDDLE = HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

	private Connection		connection	;
	private int 			rtnCode		= ErrorMessage.OK;
	private ErrorMessage	errMessage	;
	private ResourceManager resource	;

	private DBTableModel table			;
	private String		 stmtString		;
	private int 		 executeCount	= -1 ;
	private int 		 skipRowCount	;
	private int 		 maxRowCount	= HybsSystem.sysInt( "DB_MAX_ROW_COUNT" ) ;
	private boolean		 updateFlag		= true ;
	private DBEditConfig config			;		// 5.3.6.0 (2011/06/01)

	// 5.1.9.0 (2010/08/01) DB_RETRY_COUNT,DB_RETRY_TIME 廃止
	/** データ検索時の最大処理制限時間  */
	protected static final int DB_MAX_QUERY_TIMEOUT	= HybsSystem.sysInt( "DB_MAX_QUERY_TIMEOUT" ) ;

	/** 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ	{@value}  */
	protected static final int DB_FETCH_SIZE		= HybsConst.DB_FETCH_SIZE;	// 6.9.4.1 (2018/04/09)

//	/** データ検索時のフェッチサイズを設定します。 */
//	protected static final int DB_FETCH_SIZE  = HybsConst.DB_FETCH_SIZE;	// 6.9.4.1 (2018/04/09)

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

	private String	updQuery		;			// 7.2.9.1 (2020/10/23)
	private String	insQuery		;			// 7.2.9.1 (2020/10/23)
	private String	selQuery		;			// 7.4.1.0 (2021/04/23)

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	protected AbstractQuery() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * Connectionオブジェクトを外部から設定します。
	 *
	 * 通常は、Transaction と 接続先(DBID) を使用して作成した Connection を渡します。
	 * このクラスでは、Connection の close() や、ConnectionFactory への返却なども
	 * 行いません。それらは、外部処理(通常は、Transactionオブジェクト)で行います。
	 *
	 * Connection には、null は登録できません。
	 *
	 * @og.rev 6.3.6.1 (2015/08/28) 新規追加
	 *
	 * @param	conn	Connectionオブジェクト
	 */
	public void setConnection( final Connection conn ) {
		if( conn == null ) {
			final String errMsg = "Connection に null は指定できません。" + CR ;
			throw new HybsSystemException( errMsg );
		}
		connection = conn;
	}

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

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

	/**
	 * ステートメント文字列(UPDATE,INSERT)をセットします。
	 *
	 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
	 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない
	 *
	 * @param   update UPDATEステートメント文字列
	 * @param   insert INSERTステートメント文字列
	 * @param   select SELECTステートメント文字列(あれば何もしない、なければINSERT処理の判定用)
	 */
//	public void setMergeStatement( final String update , final String insert ) {
	public void setMergeStatement( final String update , final String insert , final String select ) {
		updQuery = update;
		insQuery = insert;
		selQuery = select;
	}

	/**
	 * ステートメント文字列(UPDATE,INSERT,SELECT)を取り出します。
	 *
	 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
	 * @og.rev 7.2.9.3 (2020/11/06) 早い段階でエラーにしておきます。
	 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない
	 *
	 * @return  ステートメント文字列の配列(UPDATE,INSERTの順番)
	 */
	public String[] getMergeStatement() {
	//	if( updQuery == null || insQuery == null ) {
	//		final String errMsg = "Merge処理を行うには、INSERTとUPDATEの両方のQUERYが必要です。" + CR
	//			+ " updQuery=" + updQuery + CR
	//			+ " insQuery=" + insQuery + CR
	//			+ " query   =" + stmtString ;
	//
	//		throw new UnsupportedOperationException( errMsg );
	//	}

//		return new String[] { updQuery,insQuery } ;
		return new String[] { updQuery,insQuery,selQuery } ;
	}

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

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

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

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

	/**
	 * クエリーの実行結果を返します。
	 * クエリーが失敗した場合や,CallableStatement の呼び出し等で実行件数が明確でない
	 * 場合は, -1 が返されます。
	 *
	 * @return	実行結果件数
	 */
	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;
	}

	/**
	 * データベースの最大検索件数を返します。
	 *		(初期値:DB_MAX_ROW_COUNT[={@og.value SystemData#DB_MAX_ROW_COUNT}])。
	 * データベース自体の検索は,指定されたSQLの全件を検索しますが,
	 * DBTableModelのデータとして登録する最大件数をこの値に設定します。0は無制限です。
	 * サーバーのメモリ資源と応答時間の確保の為です。
	 *
	 * @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.0 (2005/08/31) ゼロ、または、負の値は、無制限(Integer.MAX_VALUE)にする。
	 *
	 * @param	maxRowCount 最大検索件数
	 */
	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 スキップ件数
	 */
	public void setSkipRowCount( final int skipRowCount ) {
		this.skipRowCount = skipRowCount;
	}

	/**
	 * アップデートフラグをセットします。
	 * これは、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.0 (2007/07/20) メソッド名変更( getUpdateFlag() ⇒ isUpdate() )
	 *
	 * @return	 アップデートされたかどうか[true:更新処理/false:検索処理]
	 */
	public boolean isUpdate() {
		return updateFlag ;
	}

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

	/**
	 * エラーコード を取得します。
	 * エラーコード は、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;
	}

	/**
	 * 編集設定オブジェクトをセットします。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 *
	 * @param config 編集設定オブジェクト
	 */
	public void setEditConfig( final DBEditConfig config ) {
		this.config = config;
	}

	/**
	 * 編集設定オブジェクトを取得します。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 *
	 * @return 編集設定オブジェクト
	 */
	protected DBEditConfig getEditConfig() {
		return config;
	}

	//////////////////////////////////////////////////////////////////////////
	//
	//	 継承時にサブクラスから使用するメソッド類( 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.0 (2006/01/31) CLOB カラムかどうかを判定しCLOBの場合は、ストリームから値を取り出します。
	 * @og.rev 5.3.6.0 (2011/06/01) DBTableModel作成処理をDBTableModelUtilに移動＆集計機能対応
	 * @og.rev 6.3.6.1 (2015/08/28) close(),realClose() 廃止。Queryはキャッシュしません。
	 *
	 * @param	resultSet ResultSetオブジェクト
	 */
	protected void createTableModel( final ResultSet resultSet ) {
		try {
			if( config == null ) {
				table = DBTableModelUtil.makeDBTable( resultSet, getSkipRowCount(), maxRowCount, resource );
			}
			else {
				table = DBTableModelUtil.makeEditDBTable( resultSet, getSkipRowCount(), maxRowCount, resource, config );
			}

			setExecuteCount( table.getRowCount() );
		}
		catch( final SQLException ex ) {
			final String errMsg = "テーブルモデルを作成できませんでした。";
			throw new HybsSystemException( errMsg,ex );		// 3.5.5.4 (2004/04/15) 引数の並び順変更
		}
	}

	/**
	 * ConnectionFactory.connection( String ); を利用して,Connection
	 * オブジェクトを取り出します。
	 *
	 * コネクションプールが一杯の場合は、即エラーになります。
	 *
	 * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 5.1.9.0 (2010/08/01) transaction 属性追加。
	 * @og.rev 6.3.6.1 (2015/08/28) transaction 属性廃止。内部のConnectionを返します。
	 *
	 * @return	コネクション
	 */
	protected Connection getConnection() {
		return connection;
	}

	/**
	 * この接続が、PreparedStatement#getParameterMetaData() を使用するかどうかを判定します。
	 *
	 * ConnectionFactory#useParameterMetaData(String) の結果を返します。(postgreSQL対応)
	 *
	 * ※ 暫定処理です。もっと、良い方法を考える必要があります。
	 *
	 * @og.rev 5.3.8.0 (2011/08/01) 新規追加
	 * @og.rev 6.3.6.1 (2015/08/28) 内部変数にconnIDが無くなったため、直接所得することになりました。
	 * @og.rev 6.4.2.0 (2016/01/29) ex.printStackTrace() を、ThrowUtil#ogStackTrace(Throwable) に置き換え。
	 *
	 * @return	使用する場合：true / その他:false
	 * @see org.opengion.fukurou.db.ConnectionFactory#useParameterMetaData(String)
	 */
	protected boolean useParameterMetaData() {
	//	return ConnectionFactory.useParameterMetaData( connID );
		try {
			return "PostgreSQL".equalsIgnoreCase( connection.getMetaData().getDatabaseProductName() );
		}
		catch( final Throwable th ) {
			System.err.println( ThrowUtil.ogStackTrace( th ) );				// 6.4.2.0 (2016/01/29)
		}
		return false ;
	}

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

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