/*
 * 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 org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.fukurou.db.DBUtil;

import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.StringUtil ;
import static org.opengion.fukurou.util.StringUtil.nval ;
import org.opengion.fukurou.model.Formatter;

import java.util.Locale ;
import java.util.List ;
import java.util.ArrayList ;

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

/**
 * 【廃止】登録すべきデータのマスタ存在チェックを行うためのタグです(通常はentry.jspでupdateタグの直前で使用します)。
 *
 * この要素の内容に、SQL文を記述します。
 * names に対応するカラム名を、カンマ区切りで複数与えます。その値を、DBTableModel
 * より、取得し、先のSQL文の ? に値を設定します。
 * または、引数部に、[カラム名]を用いたHybs拡張SQL文を指定することも可能です。
 *
 * 値の取得は、先に選択された行のみについて、実行されます。
 * exist 属性に指定された 、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、
 * の値は、チェック方法を設定しています。
 * いずれの場合も、成立時は、正常とみなします。
 * (｢true:存在する｣ には、データが存在した場合に、ＯＫで、なければエラーです。)
 *
 * @og.formSample
 * ●形式：
 *       ・&lt;og:tableExist
 *                    command   = "{&#064;command}"
 *                    names     = "[…]"
 *                    from      = "…"                           必須
 *                    where     = "…"                           必須
 *                    exist     = "[auto|true|false|one|notuse]" 必須
 *                    errRemove = "[true|false]"
 *         /&gt;
 *
 * ●body：なし
 *
 * ●Tag定義：
 *   &lt;og:tableExist
 *       command            【廃止】コマンド(ENTRY)をセットします
 *       scope              【TAG】キャッシュする場合のスコープ[request/page/session/applicaton]を指定します(初期値:session)
 *       names              【廃止】引数にセットすべき データの名称(カラム名)をCSV形式で複数指定します
 *       from             ○【廃止】チェックするデータベース名(from 句)を指定します(必須)。
 *       where            ○【廃止】チェックする検索条件(where句)を指定します(必須)。
 *       exist              【廃止】データベースのチェック方法(auto/true/false/one/notuse)を指定します(初期値:｢auto:自動｣)
 *       tableId            【廃止】(通常は使いません)結果をDBTableModelに書き込んで、sessionに登録するときのキーを指定します
 *       dbid               【廃止】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します
 *       errRemove          【廃止】エラー時の選択行を取り除いて継続処理を行うかどうか[true/false]を指定します(初期値:false)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   /&gt;
 *
 * ●使用例
 *       ・&lt;og:tableExist
 *                    command = "{&#064;command}"
 *                    names   = "USERID,SYSTEM_ID"
 *                    from    = "GE10"
 *                    where   = "USERID=? AND SYSTEM_ID=?"
 *                    exist   = "true"         /&gt;
 *
 *          ・where 条件の ? 文字に、names で指定したカラム名の値が、DBTableModelより
 *            取得されます。
 *            値の取得は、先に選択された行のみについて、実行されます。
 *          ・exist 属性の値に応じて、チェック方法が異なります。
 *            auto , true , false , one , notuse が指定できます。
 *          ・テーブルは、１つのみ指定できます。複数指定や、UNIONで結合する場合は、
 *            ビュー等を作成して対応してください。
 *
 *       ・&lt;og:tableExist
 *                    command = "{&#064;command}"
 *                    from    = "GE10"
 *                    where   = "USERID=[USERID] AND SYSTEM_ID=[SYSTEM_ID]"  /&gt;
 *
 *          ・where 条件の [カラム名] 文字に、DBTableModelより値がセットされます。
 *          ・exist は、初期値(auto)になります。内部のA,C,Dに応じて自動判別します。
 *
 * @og.rev 3.5.0.0 (2003/09/17) 新規作成
 * @og.group (廃止)ＤＢ登録
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 * @deprecated 代わりに dataCheckタグを使用してください
 */
@Deprecated public class TableExistTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "4.0.0.0 (2007/11/28)" ;

	private static final long serialVersionUID = 400020071128L ;

	/** command 引数に渡す事の出来る コマンド  エントリー {@value} */
	public static final String CMD_ENTRY   = "ENTRY" ;

	/** command 引数に渡す事の出来る コマンド リスト  */
	private static final String COMMAND_LIST = CMD_ENTRY;

	// 3.5.6.0 (2004/06/18) すべてを protected から private に変更します。
	private transient DBTableModel	table 		= null;
	private transient ErrorMessage	errMessage	= null;
	private String			tableId		= HybsSystem.TBL_MDL_KEY;
	// 4.0.0.0 (2007/10/10) dbid の初期値を、"DEFAULT" から null に変更
//	private String			dbid		= "DEFAULT";
	private String			dbid		= null;
	private String			command		= CMD_ENTRY;
	private String			sql			= null;
	private String			names		= null;
	private String			from		= null;
	private String			where		= null;
	private String			exist		= "auto";
	private boolean			errRemove	= false;

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @og.rev 3.7.1.0 (2005/04/15) notuse 値を追加
	 *
	 * @return	後続処理の指示(SKIP_BODY)
	 */
	@Override
	public int doStartTag() {
		if( !exist.equalsIgnoreCase( "notuse" ) ) {
			table = (DBTableModel)getObject( tableId );
			if( table != null && table.getRowCount() > 0 && check( command, COMMAND_LIST ) ) {
				sql = makeSQLString();
				execute( sql );
			}
		}

		return(SKIP_BODY);				// Body を評価しない
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 3.5.4.2 (2003/12/15) HTMLTableViewForm クラス名変更(⇒ ViewForm_HTMLTable)
	 * @og.rev 3.5.4.4 (2004/01/16) エラー結果を表示するテーブル形式のフォーム修正
	 * @og.rev 3.5.5.2 (2004/04/02) TaglibUtil.makeHTMLErrorTable メソッドを利用
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)

		int rtnCode = EVAL_PAGE;

		if( CMD_ENTRY.equals( command ) && errMessage != null && ! errMessage.isOK() ) {
		// 3.5.5.2 (2004/04/02) TaglibUtil.makeHTMLErrorTable メソッドを利用
			if( !errRemove ) {
				rtnCode = SKIP_PAGE ;
				jspPrint( TaglibUtil.makeHTMLErrorTable( errMessage,getResource() ) );
			}
		}

		return( rtnCode );
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 4.0.0.0 (2007/10/10) dbid の初期値を、"DEFAULT" から null に変更
	 */
	@Override
	protected void release2() {
		super.release2();
		tableId			= HybsSystem.TBL_MDL_KEY;
//		dbid			= "DEFAULT";
		dbid			= null;
		command			= CMD_ENTRY;
		table 			= null;
		sql				= null;
		names 			= null;
		errMessage		= null;
		exist			= "auto";
		errRemove		= false;
	}

	/**
	 * SQL文を構築します。
	 *
	 * @return 構築された、SQL文
	 */
	private String makeSQLString() {
		StringBuilder rtn = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
		rtn.append( "select count(*) from " );
		rtn.append( from );
		rtn.append( " where " );
		rtn.append( where );

		return rtn.toString();
	}

	/**
	 * Query を実行します。
	 *
	 * @param   sql 検索文字列
	 *
	 * @og.rev 3.8.6.0 (2006/09/29) exist 属性の one を ｢one:ひとつのみ｣から｢one:ひとつ以下｣に変更
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.0.0.0 (2005/01/31) getParameterRows() を使用するように変更
	 * @og.rev 4.0.0.0 (2007/11/28) 論理処理の不具合修正(カッコの付ける位置間違い)
	 */
	private void execute( final String sql ) {
		errMessage = new ErrorMessage( "Database Exist Data Error!" );

		final String query ;
		final int[]  clmNo ;

		int[] rowNo = getParameterRows();		// 4.0.0 (2005/01/31)
		if( rowNo.length == 0 ) { return; }

		// names なしの場合は、Query より取得する。  4.0.0 (2005/01/31)
		if( names == null ) {
//			QueryForm form = new QueryForm( sql,table );
//			clmNo = form.getColumnNos();
//			query = form.getStatement();
//			names = StringUtil.array2csv( form.getColumnNames() );

			Formatter format = new Formatter( table );
			format.setFormat( sql );
			query = format.getQueryFormatString();
			clmNo = format.getClmNos();
			names = StringUtil.array2csv( table.getNames() );
		}
		else {
			clmNo = getTableColumnNo( StringUtil.csv2Array( names ) );
			query = sql;
		}

		int row;
		boolean okFlag ;
		List<Integer> list = new ArrayList<Integer>();
		String[] values ;
		for( int j=0; j<rowNo.length; j++ ) {
			okFlag = true;
			row = rowNo[j];
			values = getTableModelData( clmNo,row );
			int cnt = DBUtil.dbExist( query,values,getApplicationInfo(),dbid );

			String modifyType = table.getModifyType( row );
			if( ( exist.equalsIgnoreCase( "true" ) ||
				( exist.equalsIgnoreCase( "auto" ) && (
						DBTableModel.UPDATE_TYPE.equals( modifyType ) ||
						DBTableModel.DELETE_TYPE.equals( modifyType ) ) ) ) && cnt <= 0 ) {
				// ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
				String vals = StringUtil.array2csv( values );
				errMessage.addMessage( row,ErrorMessage.NG,"ERR0025",names,vals );
				okFlag = false;
			}
			// 4.0.0.0 (2007/11/28) 論理処理の不具合修正(カッコの付ける位置間違い)
			else if( ( exist.equalsIgnoreCase( "false" ) ||
					 ( exist.equalsIgnoreCase( "auto" ) &&
							DBTableModel.INSERT_TYPE.equals( modifyType ) ) ) && cnt > 0 ) {
				// ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
				String vals = StringUtil.array2csv( values );
				errMessage.addMessage( row,ErrorMessage.NG,"ERR0026",names,vals );
				okFlag = false;
			}
			else if( exist.equalsIgnoreCase( "one" ) && cnt > 1 ) {
				// ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
				String vals = StringUtil.array2csv( values );
				errMessage.addMessage( row,ErrorMessage.NG,"ERR0027",names,vals );
				okFlag = false;
			}
			if( errRemove && okFlag ) { list.add( row ); }
		}
		if( errRemove ) {
			Integer[] in = list.toArray( new Integer[list.size()] );
			int[] newRowNo = new int[in.length];
			for( int i=0; i<in.length; i++ ) {
				newRowNo[i] = in[i].intValue();
			}
			setParameterRows( newRowNo );
		}
	}

	/**
	 * 【廃止】(通常は使いません)結果をDBTableModelに書き込んで、sessionに登録するときのキーを指定します。
	 *
	 * @og.tag
	 *		(初期値:HybsSystem#TBL_MDL_KEY[={@og.value org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @param	id sessionに登録する時の ID
	 * @deprecated クラスが廃止されました。
	 */
	@Deprecated public void setTableId( final String id ) {
		tableId   = nval( getRequestParameter( id ),tableId );	// 3.8.0.9 (2005/10/17)
	}

	/**
	 * 【廃止】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します。
	 *
	 * @og.tag Queryオブジェクトを作成する時のDB接続IDを指定します。
	 *
	 * @param	id データベース接続ID
	 * @deprecated クラスが廃止されました。
	 */
	@Deprecated public void setDbid( final String id ) {
		dbid = nval( getRequestParameter( id ),dbid );
	}

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

	/**
	 * 【廃止】引数にセットすべき データの名称(カラム名)をCSV形式で複数指定します。
	 *
	 * @og.tag
	 * 複数ある場合は、カンマ区切り文字で渡します。
	 * 引数をnames ではなく、[カラム名]形式で直接指定するほうが、SQL文が判りやすくなります。
	 *
	 * @param	nm 引数の名称(複数ある場合は、カンマ区切り文字)
	 * @deprecated クラスが廃止されました。
	 */
	@Deprecated public void setNames( final String nm ) {
		names = nval( getRequestParameter( nm ),names );
	}

	/**
	 * 【廃止】チェックするデータベース名(from 句)を指定します。
	 *
	 * @og.tag
	 * from 句 に指定するデータベース名です。
	 *
	 * @param	fm データベースID
	 * @deprecated クラスが廃止されました。
	 */
	@Deprecated public void setFrom( final String fm ) {
		from = nval( getRequestParameter( fm ),from );
	}

	/**
	 * 【廃止】チェックする検索条件(where句)を指定します。
	 *
	 * @og.tag
	 * where 区 に指定する検索条件です。? の部分に、names 属性で指定した
	 * カラムのデータが、DBTableModelより取り出されて適用されます。
	 * または、[カラム名]形式で、直接指定することもできます。その場合は、
	 * name 属性は指定する必要がありません。
	 * [カラム名]の前後に、(')シングルコーテーションは、不要です。
	 *
	 * @param	wr 検索条件(where句)
	 * @deprecated クラスが廃止されました。
	 */
	@Deprecated public void setWhere( final String wr ) {
		where = nval( getRequestParameter( wr ),where );
	}

	/**
	 * 【廃止】データベースのチェック方法(auto/true/false/one/notuse)を指定します(初期値:｢auto:自動｣)。
	 *
	 * @og.tag
	 * exist 属性に指定された 、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、
	 * の値は、いずれの場合も、成立時は、正常とみなします。
	 * ｢auto:自動｣は、DBTableModeleのmodifyType(A,C,D)に応じて、チェックします。
	 * A,C,D は、entryタグにコマンドを渡してデータを作成したときに、内部で作成されます。
	 * notuse は、チェックを行いません。これは、このタグを共有使用する場合に、外部で
	 * チェックを行うかどうかを指定できるようにするために使用します。
	 * (｢true:存在する｣ には、データが存在した場合に、ＯＫで、なければエラーです。)
	 * 初期値は、｢auto:自動｣です。
	 *
	 * @param	ext チェック方法(｢auto:自動｣、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、｢notuse:チェックしない｣)
	 * @deprecated クラスが廃止されました。
	 */
	@Deprecated public void setExist( final String ext ) {
		exist = nval( getRequestParameter( ext ),exist );
		if( !"auto".equalsIgnoreCase( exist ) &&
			!"true".equalsIgnoreCase( exist ) &&
			!"false".equalsIgnoreCase( exist ) &&
			!"one".equalsIgnoreCase( exist ) &&
			!"notuse".equalsIgnoreCase( exist ) ) {
				String errMsg = "exist 属性は、(auto,true,false,one,notuse)を指定してください。 [" + exist + "]" + HybsSystem.CR ;
				throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【廃止】エラー時の選択行を取り除いて継続処理を行うかどうか[true/false]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * exist 属性に指定された 、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、
	 * に対して、エラーが発生した選択行番号を、取り除いて以下の処理を継続するかどうかを
	 * 指定します。
	 * true に設定した場合は、エラーデータを削除し、継続処理を行うことができます。
	 * flase の場合は、エラーデータを表示して、継続処理を停止します。
	 * 初期値は、｢false:エラー時停止｣です。
	 *
	 * @param	flag エラー時の継続処理 [true:エラー行番号を取り除き継続処理/false:エラー時停止]
	 * @deprecated クラスが廃止されました。
	 */
	@Deprecated public void setErrRemove( final String flag ) {
		errRemove = nval( getRequestParameter( flag ),errRemove );
	}

	/**
	 *  カラム名配列(String[])より、対応するカラムNo配列(int[])を作成します。
	 *
	 * @param	nameArray カラム名配列
	 *
	 * @return	カラムNo配列
	 */
	private int[] getTableColumnNo( final String[] nameArray ) {
		int[] clmNo = new int[ nameArray.length ];
		for( int i=0; i<clmNo.length; i++ ) {
			clmNo[i] = table.getColumnNo( nameArray[i] );
		}
		return clmNo;
	}

	/**
	 *  指定の行番号の、カラムNo配列(int[])に対応した値の配列を返します。
	 *
	 * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を
	 * 処理の対象とします。
	 *
	 * @param	clmNo	カラムNo配列
	 * @param	row		行番号
	 *
	 * @return	行番号とカラムNo配列に対応した、値の配列
	 */
	private String[] getTableModelData( final int[] clmNo,final int row ) {
		String[] values = new String[ clmNo.length ];
		for( int i=0; i<values.length; i++ ) {
			values[i] = table.getValue( row,clmNo[i] ) ;
		}
		return values;
	}

	/**
	 * シリアライズ用のカスタムシリアライズ書き込みメソッド
	 *
	 * @og.rev 4.0.0.0 (2006/09/31) 新規追加
	 * @serialData
	 *
	 * @param	strm	ObjectOutputStreamオブジェクト
	 */
	private void writeObject( final ObjectOutputStream strm ) throws IOException {
		strm.defaultWriteObject();
	}

	/**
	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
	 *
	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
	 *
	 * @og.rev 4.0.0.0 (2006/09/31) 新規追加
	 * @serialData
	 *
	 * @param	strm	ObjectInputStreamオブジェクト
	 * @see #release2()
	 */
	private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
		strm.defaultReadObject();
	}

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