/*
 * Copyright (C) 2005 NTT DATA Corporation
 * 
 */
package org.postgresforest.vm.err;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.postgresforest.Driver;

/**
 * エラー判定クラス.
 * プロパティファイル（org.postgresforest.disterror）を読み込み、定義されている
 * パターンで照合し、切り離し・アプリへスローの判断を行う。
 */
public class DistError {


	private static final String ERR_PROPERTY = "org.postgresforest.vm.err.disterror";
	private static final String ERR_ID_KEY = "error.broken.status";
	private static final String ERR_MSG_KEY = "error.broken";

	private static final String ERR_ROLLBACK_ID_KEY = "error.rollback.status";//	Ver3.1 ロールバック対象判別用
	private static final String ERR_ROLLBACK_MSG_KEY = "error.rollback";//	Ver3.1 ロールバック対象判別用

	private static final int FIND = 0;
	private static final int MATCH = 1;

	private static DistError m_instance = null;
	private ArrayList m_brokenMatcherList;
	private ArrayList m_idMatcherList;	//Ver2.0 ID判別用

	private ArrayList m_rollBackMatcherList; //Ver3.1 ロールバック対象判別用
	private ArrayList m_rollBackIdMatcherList;	//Ver3.1 ロールバック対象判別用


	/**
	 * コンストラクタ
	 */
	public DistError() {
		//メッセージリストの取り込み
		ResourceBundle bundle;
		try
		{
			bundle = ResourceBundle.getBundle(ERR_PROPERTY);
		}
		catch (MissingResourceException e)
		{
			// translation files have not been installed.
			bundle = null;
		}
		
		m_brokenMatcherList = readErrProperty(ERR_MSG_KEY, bundle);
		m_idMatcherList = readErrProperty(ERR_ID_KEY, bundle);
		
		//Ver3.1 ロールバック対象判別用
		m_rollBackMatcherList = readErrProperty(ERR_ROLLBACK_MSG_KEY, bundle);
		m_rollBackIdMatcherList = readErrProperty(ERR_ROLLBACK_ID_KEY, bundle);

		if (Driver.logInfo)
			Driver.info("The separation error message list was read.");
	}

	/**
	 * 切り離しエラーのパターンリスト読み込み
	 * 
	 * @param key プロパティのキー文字列
	 * @param bundle　プロパティを読み込んだResourceBundle
	 * @return　切り離しエラーのパターンリスト
	 * @since 2.0
	 */
	private ArrayList readErrProperty(String key, ResourceBundle bundle) {

		//切り離しエラーのパターンリスト作成
		String pattern = bundle.getString(key);
		
		//空白をセパレータとしてパーターン文字列リストの作成
		String[] patternList = pattern.split("\\s+");
		
		//パターン文字列をMatcherリストに設定
		ArrayList matcherList = new ArrayList(patternList.length);
		for (int i = 0; i < patternList.length; i++) {
			Pattern ps = Pattern.compile(patternList[i]);
			Matcher mt = ps.matcher("");	//ダミーセット
			matcherList.add(mt);
			
		}
		return matcherList;
	}

	/**
	 * インスタンス取得.
	 * シングルトンでインスタンスを生成する 
	 */
	private synchronized final static DistError getInstance() {

		if (m_instance == null)
		{
			m_instance = new DistError();
		}
		return m_instance;
	}

	/**
	 * エラー判定.
	 * @param ex - 例外クラス
	 * @return true - サーバを切り離す（サーバ状態を故障中にする）すべきエラー
	 *          false - アプリケーションへthrowするエラー
	 */
	public final static boolean isBroken(SQLException ex)
	{


		//インスタンス取得
		DistError dist = DistError.getInstance();
		
		//Ver2.0
		String status = ex.getSQLState();
		if (Driver.logDebug)
			Driver.debug("Error judging SQLException = " + status, ex);
		if(ex.getSQLState() != null){
			//判定
			return dist._isBroken(status);
		}

		
		//判定
		return dist._isBroken(ex);
	}


	/**
	 * エラー判定（メッセージ判別時のラッパー）
	 * @param ex - 例外クラス
	 * @return true - サーバを切り離す（サーバ状態を故障中にする）すべきエラー
	 *          false - アプリケーションへthrowするエラー
	 */
	private final boolean _isBroken(SQLException ex){


		//SQLExceptionからメッセージを取り出し
		String msg = ex.getMessage();
		//エラーメッセージでエラー判定
		return _isBroken(msg,m_brokenMatcherList, FIND); 

	}


	/**
	 * エラー判定（ステータス判別時のラッパー）
	 * @param status - SQLステータス
	 * @return true - サーバを切り離す（サーバ状態を故障中にする）すべきエラー
	 *          false - アプリケーションへthrowするエラー
	 * @since 2.0
	 */
	private final boolean _isBroken(String status){

		//エラーステータスでエラー判定
		return _isBroken(status,m_idMatcherList, MATCH); 

	}

	/**
	 * 
	 * エラー判定（実体）
	 * @param status	ステータス
	 * @param matcherList　判定用パターンリスト
	 * @param type　メッセージ／ステータス区分
	 * @return true - サーバを切り離す（サーバ状態を故障中にする）すべきエラー
	 *          false - アプリケーションへthrowするエラー
	 */
	private final static boolean _isBroken(String status, ArrayList matcherList, int type){
		if(Driver.logDebug)
			Driver.debug("_isBroken(String status, ArrayList matcherList) in..");

		boolean ret = false;

		//メッセージをパターンリストと照合
		Iterator iter= matcherList.iterator();
		while (iter.hasNext()) {
			Matcher mt = (Matcher) iter.next();
			//Ver3.1 Matcherはスレッドセーフではないので、同期
			synchronized (mt) {
				mt.reset(status);
				if( type == FIND ){
					ret = mt.find();
				}else {
					ret = mt.matches();
				}
            }

			if(ret == true){
				break;
			}
			
		}

		if(Driver.logDebug){
			Driver.debug("  ret:" + ret);
			Driver.debug("_isBroken(String status, ArrayList matcherList) out..");
		}
		return ret;
	}
	
	/**
	 * エラー判定.
	 * @param ex - 例外クラス
	 * @return true - rollbackすべきエラー
	 *          false - その他のエラー
	 * @since 3.1
	 */
	public final static boolean isRollBack(SQLException ex)
	{


		//インスタンス取得
		DistError dist = DistError.getInstance();
		
		//Ver2.0
		String status = ex.getSQLState();
		if (Driver.logDebug)
			Driver.debug("Error judging SQLException = " + status, ex);
		if(ex.getSQLState() != null){
			//判定
			return dist._isRollBack(status);
		}

		
		//判定
		return dist._isRollBack(ex);
	}
	
	/**
	 * エラー判定（メッセージ判別時のラッパー）
	 * @param ex - 例外クラス
	 * @return true - rollbackすべきエラー
	 *          false - その他のエラー
	 * @since 3.1
	 */
	private final boolean _isRollBack(SQLException ex){


		//SQLExceptionからメッセージを取り出し
		String msg = ex.getMessage();
		//エラーメッセージでエラー判定
		return _isBroken(msg,m_rollBackMatcherList, FIND); 

	}


	/**
	 * エラー判定（ステータス判別時のラッパー）
	 * @param status - SQLステータス
	 * @return true - rollbackすべきエラー
	 *          false - その他のエラー
	 * @since 3.1
	 */
	private final boolean _isRollBack(String status){

		//エラーステータスでエラー判定
		return _isBroken(status,m_rollBackIdMatcherList, MATCH); 

	}

}
