/*********************************************************************
*   All Rights reserved,Copyright (c) K-Opticom
**********************************************************************
*＜プログラム内容＞
*   システム名      ：eo顧客基幹システム
*   モジュール名    ：JPCejbEntityChecker
*   ソースファイル名：JPCejbEntityChecker.java
*   作成者          ：富士通
*   日付            ：2011年04月07日
*＜機能概要＞
*   内部スキーマ関連チェック部品
*＜修正履歴＞
*   バージョン  修正日      修正者      修正内容
*   v1.00.00    2011/04/07  FJ）根本    新規作成
*
**********************************************************************/

package eo.ejb.common.entity;

import java.lang.reflect.Method;

import com.fujitsu.futurity.model.base.CAANFinderException;
import com.fujitsu.futurity.model.base.CAANMsg;
import com.fujitsu.futurity.model.base.CAANRuntimeException;
import com.fujitsu.futurity.model.ejb.common.JSYejbLog;
import com.fujitsu.futurity.model.ejb.common.fw.TemplateSQLEntity;

/**
 * 内部スキーマ関連チェック部品クラスです。<p>
 * <BR>
 * @author 富士通
 */
public class JPCejbEntityChecker extends TemplateSQLEntity
{

	/** スキーマ名 */
	private String schemaName = null;

	/** スキーマ定義情報 */
	private Object[][] contents = null;

	/** テーブル名 */
	private String tableName = null;

	/** スキーマ定義情報の取得メソッド名 */
	private static final String GET_CONTENTS = "getContents";

	/** スキーマテーブル名の取得メソッド名 */
	private static final String GET_TABLE = "getTableName";

	/** 更新年月日時分秒項目名(英名) */
	private static final String UPD_DTM = "UPD_DTM";

	/** 更新オペレータアカウント項目名(英名) */
	private static final String UPD_OPEACNT = "UPD_OPEACNT";

	/** 更新運用年月日項目名(英名) */
	private static final String UPD_UNYO_YMD = "UPD_UNYO_YMD";

	/** 更新処理ID項目名(英名) */
	private static final String UPD_TRN_ID = "UPD_TRN_ID";

	/** パッケージ名 */
	private static final String PACKAGE_NAME = "eo.ejb.cbm.entity.";

	/** ETメッセージ接尾辞 */
	private static final String SUFFIX_ETMSG = "ETMsg";

	/** LogigalEntity接尾辞 */
	private static final String SUFFIX_LE = "LE";

	/** 項目名(世代登録年月日時分秒) */
	private static final String GENE_ADD_DTM = "GENE_ADD_DTM";

	/** メソッド名(findByCondition) */
	private static final String FIND_BY_CONDITION = "findByCondition";

	/**
	 * 新しいJPCejbEntityCheckerを作成します。
	 * <br>
	 */
	public JPCejbEntityChecker()
	{
	}

	/**
	 * 新しいJPCejbEntityCheckerを作成します。
	 * <br>
	 * @param arg0 スキーマ名
	 */
	public JPCejbEntityChecker(String arg0)
	{
		super();
		setSchemaInfo(arg0);
	}

	/**
	 * スキーマ名を取得します。
	 * <br>
	 * @return スキーマ名
	 */
	protected String getSchemaName()
	{
		return this.schemaName;
	}

	/**
	 * スキーマの定義情報を取得します。
	 * <br>
	 * @return スキーマの定義情報
	 */
	protected Object[][] getSchemaContents()
	{
		return this.contents;
	}

	/**
	 * スキーマのテーブル名を取得します。
	 * <br>
	 * @return スキーマのテーブル名
	 */
	protected String getTableName()
	{
		return this.tableName;
	}

	/**
	 * スキーマ情報を設定します。
	 * パラメータのスキーマ名から対象スキーマの定義情報、テーブル名を設定します。
	 * <br>
	 * @param arg0 スキーマ名
	 */
	private void setSchemaInfo(String arg0)
	{
		this.schemaName = arg0;
		setSchemaContents();
		setTableName();
	}

	/**
	 * スキーマの定義情報を設定します。
	 * <br>
	 */
	private void setSchemaContents()
	{
		try
		{
			Class<?> cls = Class.forName(this.schemaName);
			Object instance = cls.newInstance();
			Method method = cls.getMethod(GET_CONTENTS, (Class<?>[])null);
			this.contents = (Object[][])method.invoke(instance, (Object[])null);
		}
		catch (Exception e)
		{
			throw new CAANRuntimeException(e);
		}
	}

	/**
	 * スキーマのテーブル名を設定します。
	 * <br>
	 */
	private void setTableName()
	{
		try
		{
			Class<?> cls = Class.forName(this.schemaName);
			Object instance = cls.newInstance();
			Method method = cls.getMethod(GET_TABLE, (Class<?>[])null);
			this.tableName = (String)method.invoke(instance, (Object[])null);
		}
		catch (Exception e)
		{
			throw new CAANRuntimeException(e);
		}
	}

	/**
	 * プライマリキーによる存在チェックを行います。
	 * <br>
	 * @param inMsg 処理対象のメッセージキャリア
	 * @param schemaID スキーマID
	 * @return データが存在する場合はtrue、存在しない場合はfalse
	 */
	public boolean isExistingPrimaryKey(CAANMsg inMsg, String schemaID)
	{
		// メッセージキャリアのnullチェック
		chkMsg(inMsg);
		
		try
		{
			findByPrimaryKey(inMsg);
		}
		catch (CAANFinderException cfe)
		{
			// レコードが存在しない場合
			return false;
		}
		
		return true;
	}

	/**
	 * 世代管理スキーマに対する、世代管理項目を除いたプライマリキーによる存在チェックを行います。
	 * <br>
	 * @param inMsg 処理対象のメッセージキャリア
	 * @param schemaID スキーマID
	 * @return データが存在する場合はtrue、存在しない場合はfalse
	 */
	public boolean isExistingPrimaryKeyForGene(CAANMsg inMsg, String schemaID)
	{
		// メッセージキャリアのnullチェック
		chkMsg(inMsg);
		
		CAANMsg[] msg = findByPKWithoutGeneCol(inMsg, schemaID);
		
		if (0 == msg.length)
		{
			return false;
		}
		
		return true;
	}

	/**
	 * タイムスタンプチェックを行います。
	 * <br>
	 * @param inMsg 処理対象のメッセージキャリア
	 * @param schemaID スキーマID
	 * @return データが存在する場合はtrue、存在しない場合はfalse
	 */
	public boolean isNotUpdated(CAANMsg inMsg, String schemaID)
	{
		// メッセージキャリアのnullチェック
		chkMsg(inMsg);
		
		try
		{
			CAANMsg outMsg = findByPrimaryKey(inMsg);

			if (!(inMsg.getString(UPD_DTM).equals(outMsg.getString(UPD_DTM))))
			{
				// 排他エラーの出力
				StringBuffer msg = new StringBuffer();
				msg.append("排他エラー発生:タイムスタンプ")
					.append(",対象スキーマ=").append(this.tableName)
					.append(",入力更新年月日時分秒=").append(inMsg.getString(UPD_DTM))
					.append(",更新年月日時分秒=").append(outMsg.getString(UPD_DTM))
					.append(",更新オペレータアカウント=").append(outMsg.getString(UPD_OPEACNT))
					.append(",更新処理ID=").append(outMsg.getString(UPD_TRN_ID))
					.append(",更新運用年月日=").append(outMsg.getString(UPD_UNYO_YMD));
						   
				JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), msg);

				return false;
			}
		}
		catch (CAANFinderException cfe)
		{
			// レコードが存在しない場合
			JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), "排他エラー発生:レコード未存在,対象スキーマ=" + this.tableName);
			return false;
		}

		return true;
	}

	/**
	 * 世代管理スキーマに対するタイムスタンプチェックを行います。
	 * <br>
	 * @param inMsg 処理対象のメッセージキャリア
	 * @param schemaID スキーマID
	 * @return inMsgの更新前更新年月日時分秒が最新の場合はtrue、最新でない場合はfalse
	 */
	public boolean isNotUpdatedForGene(CAANMsg inMsg, String schemaID)
	{
		// メッセージキャリアのnullチェック
		chkMsg(inMsg);
		
		CAANMsg[] compMsg = findByPKWithoutGeneCol(inMsg, schemaID);

		if (0 == compMsg.length)
		{
			// レコードが存在しない場合
			JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), "排他エラー発生:レコード未存在,対象スキーマ=" + this.tableName);
			return false;
		}

		return isSameUpdDtm(inMsg, compMsg);
	}

	/**
	 * 排他モードタイムスタンプチェックを行います。
	 * <br>
	 * @param inMsg 処理対象のメッセージキャリア
	 * @param schemaID スキーマID
	 * @return 排他処理が成功した場合はtrue、成功しなかった場合はfalse
	 */
	public boolean succeededExclusiveLock(CAANMsg inMsg, String schemaID)
	{
		// メッセージキャリアのnullチェック
		chkMsg(inMsg);
		
		// レコードロック
		try
		{
			findByKeyForUpdate(inMsg);
		}
		catch (CAANFinderException cfe)
		{
			// ロック対象のレコードが存在しない場合
			// レコード未存在エラーの出力
			JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), "排他エラー発生:レコード未存在,対象スキーマ=" + this.tableName);
			return false;
		}
		catch (CAANRuntimeException cre)
		{
			// 既に排他ロックがかかっていた場合
			// 排他ロックエラーの出力
			JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), "排他エラー発生:ロック,対象スキーマ=" + this.tableName);
			return false;
		}
		
		// タイムスタンプチェック
		if (!(isNotUpdated(inMsg, schemaID)))
		{
			return false;
		}
		
		return true;
	}

	/**
	 * 世代管理スキーマに対する排他モードタイムスタンプチェックを行います。
	 * <br>
	 * @param inMsg 処理対象のメッセージキャリア
	 * @param schemaID スキーマID
	 * @return 排他処理が成功した場合はtrue、成功しなかった場合はfalse
	 */
	public boolean succeededExclusiveLockForGene(CAANMsg inMsg, String schemaID)
	{
		// メッセージキャリアのnullチェック
		chkMsg(inMsg);
		
		CAANMsg[] msg = findByPKWithoutGeneCol(inMsg, schemaID);
		
		if (0 == msg.length)
		{
			// レコード未存在エラーの出力
			JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), "排他エラー発生:レコード未存在,対象スキーマ=" + this.tableName);
			return false;
		}
		
		// ロックをかけるレコード(更新年月日時分秒が最大のレコード)の抽出
		CAANMsg latestRec = null;
		for (int i = 0; i < msg.length; i++)
		{
			boolean latestFlg = true;
			for (int j = 0; j < msg.length; j++)
			{
				if (msg[i].getString(UPD_DTM).compareTo(msg[j].getString(UPD_DTM)) < 0)
				{
					latestFlg = false;
					break;
				}
			}
			if (latestFlg)
			{
				latestRec = msg[i];
				break;
			}
		}
		
		// 抽出したレコードをロック
		try
		{
			findByKeyForUpdate(latestRec);
		}
		catch (CAANFinderException cfe)
		{
			// ロック対象のレコードが存在しない場合
			// レコード未存在エラーの出力
			JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), "排他エラー発生:レコード未存在,対象スキーマ=" + this.tableName);
			return false;
		}
		catch (CAANRuntimeException cre)
		{
			// 既に排他ロックがかかっていた場合
			// 排他ロックエラーの出力
			JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), "排他エラー発生:ロック,対象スキーマ=" + this.tableName);
			return false;
		}
		
		// タイムスタンプチェック
		// inMsgの更新前更新年月日時分秒が最新でない場合、falseを返却
		if (!isSameUpdDtm(inMsg, msg))
		{
			return false;
		}
		
		return true;
	}

	/**
	 * エンティティのプライマリキーを格納したメッセージを取得します。<br>
	 * プライマリキーに世代管理カラム名が存在する場合は、メッセージには含めません。
	 * <br>
	 * @param inMsg エンティティの情報を格納したメッセージキャリア
	 * @param keyList プライマリキー項目のスキーマ項目名とDBカラム名のリスト
	 * @param schemaID スキーマのID
	 * @return 世代管理カラムを除いたプライマリキーを格納したメッセージ
	 */
	private CAANMsg getPKMsg(CAANMsg inMsg, String[][] keyList, String schemaID)
	{
		CAANMsg msg = new CAANMsg(PACKAGE_NAME + schemaID + SUFFIX_ETMSG);

		for (int i = 0; i < keyList[0].length; i++)
		{
			if (keyList[0][i].equals(GENE_ADD_DTM))
			{
				// 世代登録年月日時分秒を除く
				continue;
			}

			if (inMsg.containsKeyOfMsgData(keyList[0][i]))
			{
				msg.set(keyList[0][i], inMsg.getString(keyList[0][i]));
			}
		}

		return msg;
	}

	/**
	 * 世代管理カラムをのぞいたプライマリキー検索を行います。
	 * <br>
	 * @param inMsg 処理対象のメッセージキャリア
	 * @param schemaID スキーマID
	 * @return 世代管理カラム、予約管理カラムをのぞいたプライマリキー検索結果
	 */
	private CAANMsg[] findByPKWithoutGeneCol(CAANMsg inMsg, String schemaID)
	{
		CAANMsg[] outMsg = null;

		try
		{
			String[][] keyList = getKeyColumnList();
			CAANMsg msg = getPKMsg(inMsg, keyList, schemaID);

			Class<?> cls = Class.forName(PACKAGE_NAME + schemaID + SUFFIX_LE);
			Object obj = cls.newInstance();
			Method method = cls.getMethod(FIND_BY_CONDITION, CAANMsg.class);
			outMsg = (CAANMsg[])method.invoke(obj, msg);
		}
		catch (Exception e)
		{
			throw new CAANRuntimeException(e);
		}
		
		return outMsg;
	}
	
	/**
	 * 更新年月日時分秒を比較します。
	 * <br>
	 * @param inMsg 処理対象のメッセージキャリア
	 * @param compMsg[] 世代管理カラム以外のPKで取得したレコード(n件)
	 * @return inMsgの更新年月日時分秒とcompMsgの最大の更新年月日時分秒が等しい場合はtrue、等しくない場合はfalse
	 */
	private boolean isSameUpdDtm(CAANMsg inMsg, CAANMsg[] compMsg)
	{
		//最大更新年月日時分秒のmsg格納用
		CAANMsg outMsg = null;
		if (null == inMsg)
		{
			throw new IllegalArgumentException("inMsgの値が不正です。");
		}
		
		if (null == compMsg || 0 == compMsg.length)
		{
			throw new IllegalArgumentException("compMsgの値が不正です。");
		}
		
		// 最新の更新年月日時分秒を取得
		String latestUpdDtm = null;
		for (int i = 0; i < compMsg.length; i++)
		{
			boolean latestFlg = true;
			for (int j = 0; j < compMsg.length; j++)
			{
				if (compMsg[i].getString(UPD_DTM).compareTo(compMsg[j].getString(UPD_DTM)) < 0)
				{
					latestFlg = false;
					break;
				}
			}
			if (latestFlg)
			{
				latestUpdDtm = compMsg[i].getString(UPD_DTM);
				outMsg = compMsg[i];
				break;
			}
		}

		if (!(inMsg.getString(UPD_DTM).equals(latestUpdDtm)))
		{
			// 最新の更新年月日時分秒とinMsgの更新年月日時分秒が異なる場合
			// 排他エラーの出力
			StringBuffer msg = new StringBuffer();
			msg.append("排他エラー発生:タイムスタンプ")
			   .append(",対象スキーマ=").append(this.tableName)
			   .append(",入力更新年月日時分秒=").append(inMsg.getString(UPD_DTM))
			   .append(",更新年月日時分秒=").append(outMsg.getString(UPD_DTM))
			   .append(",更新オペレータアカウント=").append(outMsg.getString(UPD_OPEACNT))
			   .append(",更新処理ID=").append(outMsg.getString(UPD_TRN_ID))
			   .append(",更新運用年月日=").append(outMsg.getString(UPD_UNYO_YMD));
					   
			JSYejbLog.println(JSYejbLog.EXECUTION, getClass(), msg);
			
			return false;
		}

		return true;
	}

	/**
	 * メッセージキャリアのnullチェックを行います。
	 * <br>
	 * @param inMsg メッセージキャリア
	 */
	private void chkMsg(CAANMsg inMsg)
	{
		if (null == inMsg)
		{
			throw new IllegalArgumentException("inMsgの値が不正です。");
		}
	}
}
