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

import static org.opengion.fukurou.util.HybsConst.CR ;		// 6.1.0.0 (2014/12/26)
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.ApplicationInfo;
import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.db.ConnectionFactory;
import org.opengion.fukurou.db.DBUtil;					// 5.5.5.1 (2012/08/07)
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Arrays;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * 【EXCEL取込】雛形EXCELシートと、データEXCELシートから、指定のDBにデータを登録するクラスクラスです。
 * 雛形EXCELシートは、{&#064;カラム} で記述されており、このカラムのEXCEL上のセルの位置を元に、
 * データEXCELシートから所定のデータを読みこみ、雛形明細定義(GE57)で指定のテーブルに
 * 抜き出したデータを登録します。
 * 雛形明細定義(GE57)では、システムID+帳票ID+シート番号をキーに、読み取る対応シートや
 * シート毎にヘッダーテーブル、明細テーブルの指定、繰返必須カラムのしていなどにより、
 * 読取る方式と、書き込むテーブルを指定します。
 *
 * @og.rev 3.8.0.0 (2005/06/07) 新規追加
 * @og.group 帳票システム
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class ExcelInsert {
	private final StringBuilder errMsg ;

	// DBTableReport に対して設定する情報
	private final String EXCELIN ;		// EXCEL ファイルの取込DIR ファイル名は、要求番号.xls

	// 受け渡し変数
	private final String	SYSTEM_ID	;
	private final String	YKNO		;
	private final String	LISTID		;
	private final boolean	DEBUG		;	// 3.8.5.0 (2006/03/06) デバッグ用のフラグを追加

	// GE54,GE57 帳票定義、明細情報
	private String		MODELDIR	;		// GE54 雛形EXCELディレクトリ
	private String		MODELFILE	;		// GE54 雛形EXCELファイル名
	private String[]	SHEETNO		;		// GE57 雛形EXCELシート番号
	private String[]	SHEETREF	;		// GE57 データEXCELシート番号
	private String[]	HEADDBID	;		// GE57 ヘッダーテーブル
	private String[]	BODYDBID	;		// GE57 明細テーブル
	private String[]	LOOPCLM		;		// GE57 繰返必須カラム名

	private ExcelLayout layout		;

	// GE54,GE57 の帳票定義情報を取得するSQL文です。
	private static final String GE54_GE57_SELECT =
		"SELECT A.MODELDIR,A.MODELFILE,B.SHEETNO,B.SHEETREF,B.HEADDBID,B.BODYDBID,B.LOOPCLM" +
		" FROM GE54 A INNER JOIN GE57 B" +
		" ON   A.SYSTEM_ID = B.SYSTEM_ID AND A.LISTID = B.LISTID" +
		" WHERE A.FGJ = '1' AND B.FGJ = '1'" +
		" AND  A.SYSTEM_ID = ?" +
		" AND  A.LISTID = ?" +
		" ORDER BY B.SHEETNO" ;

	/** コネクションにアプリケーション情報を追記するかどうか指定 */
	public static final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;

	// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	private final ApplicationInfo appInfo;
	private final String DBID = HybsSystem.sys( "RESOURCE_DBID" );		// 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応

	/**
	 * コンストラクター
	 * 引数を受けとって、インスタンスを作成します。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 *
	 * @param system_id システムID
	 * @param ykno 要求番号
	 * @param listId 帳票ID
	 * @param excelinDir 出力ディレクトリ
	 * @param debug デバッグフラグ
	 */
	public ExcelInsert( final String system_id, final String ykno, final String listId, final String excelinDir, final boolean debug ) {
		SYSTEM_ID = system_id;
		YKNO	  = ykno;
		LISTID	  = listId;
		EXCELIN   = excelinDir;
		DEBUG	  = debug;
		errMsg	  = new StringBuilder();

		// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
		if( USE_DB_APPLICATION_INFO ) {
			appInfo = new ApplicationInfo();
			// ユーザーID,IPアドレス,ホスト名
			appInfo.setClientInfo( SYSTEM_ID,HybsSystem.HOST_ADRS,HybsSystem.HOST_NAME );
			// 画面ID,操作,プログラムID
			appInfo.setModuleInfo( "ExcelInsert",YKNO,LISTID );
		}
		else {
			appInfo = null;
		}
	}

	/**
	 * 変換処理を実行します。
	 *
	 * @og.rev 3.8.0.9 (2005/10/17) エラーメッセージ強化
	 *
	 * @return 結果 [true:正常/false:異常]
	 */
	public boolean execute() {
		System.out.print( "ExcelInsert Started ... " );
		boolean flag ;

		try {
			// 初期化 GE54,GE57 帳票定義マスタより必要な情報を取得します。
			flag = initialDataSet();
			if( flag ) { System.out.print( "INIT," ); }

			// 雛型ファイルの存在チェックを行います。
			// 3.5.4.9 (2004/02/25) 存在チェックエラー(原因不明)の暫定対応
			File templateExcel = null;
			if( flag ) {
				templateExcel = FileUtil.checkFile( MODELDIR, MODELFILE + ".xls" );
				flag = templateExcel != null ;		// チェックの結果が null なら、見つからなかった。
				// 3.8.0.9 (2005/10/17) エラーメッセージ強化
				if( flag ) { System.out.print( "MDL IN," ); }
				else {
					errMsg.append( "ExcelInsert MODELFILE Not Found Error!" ).append( CR );
					errMsg.append( "==============================" ).append( CR );
					errMsg.append( "MODELDIR=" ).append( MODELDIR ).append( CR ) ;
					errMsg.append( "MODELFILE=" ).append( MODELFILE ).append( ".xls" ) ;
					errMsg.append( CR ) ;
				}
			}

			// EXCELデータファイルの存在チェックを行います。
			File inputExcel = null;
			if( flag ) {
				inputExcel = FileUtil.checkFile( EXCELIN, YKNO + ".xls" );
				flag = inputExcel != null ;		// チェックの結果が null なら、見つからなかった。
				// 3.8.0.9 (2005/10/17) エラーメッセージ強化
				if( flag ) { System.out.print( "XLS IN," ); }
				else {
					errMsg.append( "ExcelInsert EXCELIN Not Found Error!" ).append( CR );
					errMsg.append( "==============================" ).append( CR );
					errMsg.append( "DIR=" ).append( EXCELIN ).append( CR ) ;
					errMsg.append( "FILE=" ).append( YKNO ).append( ".xls" ) ;
					errMsg.append( CR ) ;
				}
			}

			// 雛形ファイルより、処理対象行列を読み取ります。
			if( flag ) {
				flag = getModelData( templateExcel );
				if( flag ) { System.out.print( "MDL DT," ); }
			}

			// EXCELデータファイルを読取り、データベースに書き込みます。
			if( flag ) {
				flag = readAndInsertDB( inputExcel );
				if( flag ) { System.out.print( "IN DB," ); }
			}
		}
		catch ( RuntimeException ex ) {
			errMsg.append( "ExcelInsert Execute Exception Error!" ).append( CR );
			errMsg.append( "==============================" ).append( CR );
			errMsg.append( StringUtil.stringStackTrace( ex ) ) ;
			errMsg.append( CR ) ;
			flag = false;
		}

		System.out.println( "End." );
		return flag ;
	}

	/**
	 * 初期データセットを行います。
	 * ここでは、GE54,GE57 テーブルより必要な情報を取得します。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 *
	 * @return 結果 [true:正常/false:異常]
	 */
	private boolean initialDataSet() {
		final String[] args = new String[] { SYSTEM_ID,LISTID };
		// A.MODELDIR,A.MODELFILE,B.SHEETNO,B.SHEETREF,B.HEADDBID,B.BODYDBID,B.LOOPCLM
		final String[][] vals = DBUtil.dbExecute( GE54_GE57_SELECT,args,appInfo, DBID );	// 3.8.7.0 (2006/12/15)
		if( vals == null || vals.length == 0 ) {
			errMsg.append( "Data does not exist in GE54 table." ).append( CR )
				.append( "==============================" ).append( CR )
				.append( "SYSTEM_ID=["  ).append( SYSTEM_ID )
				.append( "] , LISTID=[" ).append( LISTID    )
				.append( ']' ).append( CR );		// 6.0.2.5 (2014/10/31) char を append する。
			return false;
		}

		MODELDIR	= StringUtil.nval( vals[0][0],MODELDIR   );
		MODELFILE	= StringUtil.nval( vals[0][1],MODELFILE  );

		if( MODELDIR  == null || MODELDIR.isEmpty() ||
			MODELFILE == null || MODELFILE.isEmpty() ) {
			errMsg.append( "MODELDIR and MODELFILE is necessary in GE54 table." ).append( CR )
				.append( "==============================" ).append( CR )
				.append( "SYSTEM_ID=[" ).append(    SYSTEM_ID  )
				.append( "] , LISTID=["    ).append( LISTID    )
				.append( "] , MODELDIR=["  ).append( MODELDIR  )
				.append( "] , MODELFILE=[" ).append( MODELFILE )
				.append( "] " ).append( CR );
			return false;
		}

		final int maxRow = vals.length;		// 先の条件判断で、最低 1 件以上存在する。
		SHEETNO		= new String[maxRow];
		SHEETREF	= new String[maxRow];
		HEADDBID	= new String[maxRow];
		BODYDBID	= new String[maxRow];
		LOOPCLM		= new String[maxRow];

		for( int row=0; row<maxRow; row++ ) {
			SHEETNO[row]	= StringUtil.nval( vals[row][2],null );
			SHEETREF[row]	= StringUtil.nval( vals[row][3],null );
			HEADDBID[row]	= StringUtil.nval( vals[row][4],null );
			BODYDBID[row]	= StringUtil.nval( vals[row][5],null );
			LOOPCLM[row]	= StringUtil.nval( vals[row][6],null );

			// SHEETNO と SHEETREF は、どちら『も』必須
			// HEADDBID と BODYDBID は、どちら『か』必須
			if( SHEETNO[row] == null || SHEETREF[row] == null ||
				( HEADDBID[row] == null && BODYDBID[row] == null ) ) {
				errMsg.append( "SHEETNO と SHEETREF は、どちら『も』必須" ).append( CR )
					.append( "HEADDBID と BODYDBID は、どちら『か』必須" ).append( CR )
					.append( "==============================" ).append( CR )
					.append( "SYSTEM_ID=[" ).append(     SYSTEM_ID     )
					.append( "] , LISTID=["    ).append( LISTID        )
					.append( "] , SHEETNO=["   ).append( SHEETNO[row]  )
					.append( "] , SHEETREF=["  ).append( SHEETREF[row] )
					.append( "] , HEADDBID=["  ).append( HEADDBID[row] )
					.append( "] , BODYDBID=["  ).append( BODYDBID[row] )
					.append( "] " ).append( CR );
				return false;
			}
		}

		return true;
	}

	/**
	 * 雛形ファイルより、対象行列を読み取ります。
	 *
	 * @param	file	雛形ファイル
	 *
	 * @return 結果 [true:正常/false:異常]
	 */
	private boolean getModelData( final File file ) {
		try {
			layout = HybsHSSFListener.makeExcelLayout( file,false );
		}
		catch( IOException ex ) {
			errMsg.append( "Template Excel File can not ModelData." ).append( CR );
			errMsg.append( "==============================" ).append( CR );
			errMsg.append( "File=" ).append( file.getAbsolutePath() );
			errMsg.append( StringUtil.stringStackTrace( ex ) );
			errMsg.append( CR );
			return false;
		}

		return true;
	}

	/**
	 * EXCELを読取り、データベースに書き込みます。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 *
	 * @param	file	EXCELファイル
	 *
	 * @return 結果 [true:正常/false:異常]
	 */
	private boolean readAndInsertDB( final File file ) {

		final ExcelDataPickup pickup = new ExcelDataPickup( layout,file,DEBUG );

		// 実際のデータシートの枚数
		final int sheetSize = pickup.getSheetSize();
		// SHEETREF に対して、実際に割り当てなおしたシート対応
		final int[] reference = makeSheetReference( sheetSize,SHEETREF );

		final DatabaseExecute exec = new DatabaseExecute();
		exec.setApplicationInfo( appInfo );		// 3.8.7.0 (2006/12/15)
		final int ykno = Integer.parseInt(YKNO) ;
		for( int shNo=0; shNo<sheetSize; shNo++ ) {
			final int ref = reference[shNo];
			if( ref < 0 ) { continue; }	// 処理対象外

			pickup.execute( Integer.parseInt( SHEETNO[ref] ),shNo,LOOPCLM[ref] ) ;

			final String headerQuery = layout.getHeaderInsertQuery( HEADDBID[ref] );
			if( headerQuery != null ) {
				exec.setStatement( headerQuery );

				final String[] headerData = layout.getHeaderInsertData( SYSTEM_ID,ykno,shNo );
				exec.dbExecute( headerData );
			}

			final String bodyQuery = layout.getBodyInsertQuery( BODYDBID[ref] );
			if( bodyQuery != null ) {
				exec.setStatement( bodyQuery );

				final List<String[]> bodyData  = layout.getBodyInsertData( SYSTEM_ID,ykno,shNo );
				for( int j=0; j<bodyData.size(); j++ ) {
					exec.dbExecute( bodyData.get(j) );
				}
			}
		}
		exec.commit();
		pickup.close();

		return true;
	}

	/**
	 * GE57 に指定のSHEETNOとSHEETREF配列より、実際にアクセスするシート番号に対応したリファレンス配列を求めます。
	 * SHEETNO は、雛形EXCELの使用するシート番号を指定します。SHEETREFは、その雛形シートを
	 * 利用して処理するデータEXCELのシートを指定します。シート番号は、０から始まります。
	 * この、データEXCELシート(SHEETREF)は、単一数、カンマ結合、LAST文字 で指定します。
	 * 単一数：雛形シートと１対１で対応するデータEXCELシート番号
	 * カンマ結合：3,4,5 や、2,5 などの複数シートをひとつの雛形シートで処理する場合に設定します。
	 * LAST文字：5,LAST や LAST と記述することで、それ以降の全データシートを雛形シートで処理します。
	 *
	 * ここでは、SHEETREF配列 を実際のデータEXCELシート数分の配列に再配置し、その元のアドレスを
	 * 指すリファレンス情報を返します。
	 * このリファレンス情報を元に、SHEETNO,HEADDBID,BODYDBID,LOOPCLM などの元の配列にアクセスし、
	 * 設定値を取得してきます。
	 *
	 * 例)
	 *  SHEETNO  = { "1","2"  ,"3","4"  ,"6" };
	 *  SHEETREF = { "1","2,6","4","5,3","8,LAST" };
	 *  HEADDBID = { "A","B"  ,"C","D"  ,"E" };
	 *  データシート数=11
	 *
	 * i=[0]  , No=[1], REF=[1]
	 * i=[1]  , No=[2], REF=[2,6]
	 * i=[2]  , No=[3], REF=[4]
	 * i=[3]  , No=[4], REF=[5,3]
	 * i=[4]  , No=[6], REF=[8,LAST]
	 * =========================
	 * REF=[0]  , Ref=[-1], SHEETNO[]   = -  , HEADDBID[]   = -
	 * REF=[1]  , Ref=[0],  SHEETNO[[0]]=[1] , HEADDBID[[0]]=[A]
	 * REF=[2]  , Ref=[1],  SHEETNO[[1]]=[2] , HEADDBID[[1]]=[B]
	 * REF=[3]  , Ref=[3],  SHEETNO[[3]]=[4] , HEADDBID[[3]]=[D]
	 * REF=[4]  , Ref=[2],  SHEETNO[[2]]=[3] , HEADDBID[[2]]=[C]
	 * REF=[5]  , Ref=[3],  SHEETNO[[3]]=[4] , HEADDBID[[3]]=[D]
	 * REF=[6]  , Ref=[1],  SHEETNO[[1]]=[2] , HEADDBID[[1]]=[B]
	 * REF=[7]  , Ref=[-1], SHEETNO[]   = -  , HEADDBID[]   = -
	 * REF=[8]  , Ref=[4],  SHEETNO[[4]]=[6] , HEADDBID[[4]]=[E]
	 * REF=[9]  , Ref=[4],  SHEETNO[[4]]=[6] , HEADDBID[[4]]=[E]
	 * REF=[10] , Ref=[4],  SHEETNO[[4]]=[6] , HEADDBID[[4]]=[E]
	 *
	 * @param	size	データシートの総件数
	 * @param	sheetRef	データEXCELシートの対応する配列(可変長引数)(単一数、カンマ結合、LAST文字 が使用可能)
	 *
	 * @return	データ件数分に再配置した、雛形EXCELシート番号配列。使用しない場合は、-1 がセット。
	 */
//	private int[] makeSheetReference( final int size,final String[] sheetRef ) {
	private int[] makeSheetReference( final int size,final String... sheetRef ) {

		int[] reference	= new int[size];
		Arrays.fill( reference ,-1 );

		int maxNo = -1;
		for( int i=0; i<sheetRef.length; i++ ) {
			final String[] temp = StringUtil.csv2Array( sheetRef[i] );
			for( int j=0; j<temp.length; j++ ) {
				if( temp[j].equals( "LAST" ) ) {
					for( int k=maxNo; k<size; k++ ) {
						reference[k]  = i ;
					}
					i=size;
					break;
				}
				else {
					final int no = Integer.parseInt(temp[j]) ;
					if( no < size ) {
						reference[no]  = i ;
						if( maxNo < no ) { maxNo = no+1; }
					}
					else {
						final String errMsg = "データシートと雛形明細定義の対応ができません。"
								+ " データシート総件数=[" + size + "] "
								+ " sheetRef[" + i + "]=" + sheetRef[i] ;
						throw new HybsSystemException( errMsg );
					}
				}
			}
		}
		return reference ;
	}

	/**
	 * エラーが存在した場合に、エラーメッセージを返します。
	 *
	 * @return エラーメッセージ String
	 * @og.rtnNotNull
	 */
	public String getErrMsg() {
		return errMsg.toString();
	}
}

/**
 * 連続した データベース処理を行う為の、管理処理クラスです。
 * ExcelInsert でのコーディングを分けるためだけのクラスです。
 *
 * オブジェクト作成時に、DEFAULT 接続を内部にキープし、setStatement( String )で
 * PreparedStatementオブジェクトを作成します。このメソッドを呼ぶまでは、
 * 同じ PreparedStatementオブジェクトを使い続けます。
 * dbExecute( String[] ) メソッドで、PreparedStatement に設定する引数配列をセットします。
 * この段階では、commit も、PreparedStatementのclose も行いませんので、連続して、
 * dbExecute( String[] ) メソッドを呼び出すことが可能です。
 * 最後に、commit() で、Connection は、プールに返されます。
 *
 * エラー時は、rollback() して、Connection は、破棄されます。
 *
 * @og.group 帳票システム
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
class DatabaseExecute {
	// 4.0.0.0 (2007/10/10) dbid の初期値を、"DEFAULT" から null に変更
	private static final String DBID = null ;

	private Connection			conn	;
	private PreparedStatement	pstmt	;
	private String				tempSql	;	// エラー時にSQL文を表示させる場合に使用します。
	private ApplicationInfo		appInfo	;

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

	/**
	 * PreparedStatementオブジェクトを作成します。
	 * 次に、このメソッドを呼ぶまでは、同じ PreparedStatementオブジェクトを使い続けます。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.0.0.1 (2007/12/03) try ～ catch ～ finally をきちんと行う。
	 *
	 * @param stmt String
	 */
	public void setStatement( final String stmt ) {
		boolean errFlag = true ;
		tempSql = stmt;
		try {
			// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
			if( conn == null ) { conn = ConnectionFactory.connection( DBID,appInfo ); }
			Closer.stmtClose( pstmt );
			pstmt = conn.prepareStatement( stmt );
			errFlag = false ;
		}
		catch (SQLException ex) {
			final String errMsg = "Statement を作成できませんでした。" + CR
						+ "SQL=[" + stmt + "]"
						+ ex.getMessage() + ":" + ex.getSQLState() ;
			throw new HybsSystemException( errMsg,ex );
		}
		finally {
			if( errFlag ) { errorFinally(); }
		}
	}

	/**
	 * Connection を commit します。
	 * このオブジェクトを終了する最後に行います。
	 *
	 */
	public void commit() {
		boolean errFlag = true ;
		try {
			conn.commit();
			errFlag = false ;
		}
		catch (SQLException ex) {
			Closer.rollback( conn );
			final String errMsg = "Connection をコミットできませんでした。" + CR
						+ ex.getMessage() + ":" + ex.getSQLState() ;
			throw new HybsSystemException( errMsg,ex );
		}
		finally {
			Closer.stmtClose( pstmt );
			if( errFlag ) { ConnectionFactory.remove( conn,DBID ); }
			else {			ConnectionFactory.close( conn,DBID );  }
			conn = null;
		}
	}

	/**
	 * PreparedStatement に設定する引数配列をセットします。
	 *
	 * この段階では、commit も、PreparedStatementのclose も行いませんので、連続して、
	 * dbExecute( String[] ) メソッドを呼び出すことが可能です。
	 *
	 * @param   args オブジェクトの引数配列(可変長引数)
	 */
//	public void dbExecute( final String[] args ) {
	public void dbExecute( final String... args ) {
		// System.out.println( StringUtil.array2csv( args ) );

		boolean errFlag = true ;
		try {
			for( int i=0; i<args.length; i++ ) {
				pstmt.setString( i+1,args[i] );
			}
			pstmt.execute();
			errFlag = false ;
		}
		catch (SQLException ex) {
			final String errMsg = "データベース処理を実行できませんでした。" + CR
						+ "ARGS=[" + StringUtil.array2csv( args ) + "]" + CR
						+ "SQL=[" + tempSql + "]"
						+ ex.getMessage() + ":" + ex.getSQLState() ;
			throw new HybsSystemException( errMsg,ex );
		}
		finally {
			if( errFlag ) { errorFinally(); }
		}
	}

	/**
	 * エラー発生時の処理
	 *
	 * PreparedStatement のクローズと、Connection の破棄を行います。
	 */
	private void errorFinally() {
		Closer.stmtClose( pstmt );
		pstmt = null;
		Closer.rollback( conn );
		ConnectionFactory.remove( conn,DBID );
		conn = null;
	}
}
