/*
 * DBAccessorImpl.java
 * Copyright (C) 2008 Cyber Beans Corporation. All rights reserved.
 */
package jp.co.cybec.cb3.accessor.dbaccess;


import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;

import jp.co.cybec.cb3.accessor.AccessorsLifeCycle;
import jp.co.cybec.cb3.exception.AccessorUnknownException;
import jp.co.cybec.cb3.exception.ConfigFileDefinitionException;
import jp.co.cybec.cb3.exception.ConfigFileIOException;
import jp.co.cybec.cb3.exception.ConfigFileNotFoundException;
import jp.co.cybec.cb3.exception.ConfigFileParseException;
import jp.co.cybec.cb3.exception.DataFileNoSuchSqlIdException;
import jp.co.cybec.cb3.util.ConfigurationLoader;
import jp.co.cybec.cb3.util.logging.ErrorCodeConstant;
import jp.co.cybec.cb3.util.logging.LogMessageConstant;
import jp.co.cybec.cb3.util.logging.LogMessageFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessException;


/**
 * データベースアクセッサクラス。<br>
 * このクラスのオブジェクトであるデータベースアクセッサは、
 * 設定ファイルの内容に従ってフレームワークにより生成され、
 * バッチサービスに提供されます。
 * バッチサービスは、データベースアクセッサを使用して
 * データベース上のデータにアクセスすることができます。
 * @author Lijuan Sun
 */
public class DBAccessorImpl extends CB3JdbcDaoSupport implements DBAccessor,AccessorsLifeCycle {

	/** ログ */
	private static final Log LOG = LogFactory.getLog(DBAccessorImpl.class);

	/** メッセージ */
	private static final LogMessageFactory MESSAGE = new LogMessageFactory(
			LogMessageConstant.CB3_MESSAGE_FILENAME);

	/** タグルールファイル */
	private static final String RULE_FILE = "dbaccessConfigRule.xml";
	/** クエリー用SQL文をキャッシュする */
	private Map<String, String> selectSqlCache = new HashMap<String,String>();
	/** インサート用SQL文をキャッシュする */
	private Map<String, String> insertSqlCache = new HashMap<String,String>();
	/** 更新用SQL文をキャッシュする */
	private Map<String, String> updateSqlCache = new HashMap<String,String>();
	/** 削除用SQL文をキャッシュする */
	private Map<String, String> deleteSqlCache = new HashMap<String,String>();
	/** コミット単位をキャッシュする */
	private Map<String, String> commitLimitCountMap = new HashMap<String,String>();
	/** 件数カウントをキャッシュする */
	private Map<String, Integer> commitCountMap = new HashMap<String,Integer>();

	/**
	 *
	 * データアクセス設定ファイルを読み込む。
	 *
	 * @param key String 設定ファイル名
	 * @throws ConfigFileNotFoundException 設定ファイルが存在しない場合に発生します。
	 * @throws ConfigFileDefinitionException 設定ファイルの間違いにより発生します。
	 * @throws ConfigFileIOException 設定ファイル読み込み時のIOエラーにより発生します。
	 * @throws ConfigFileParseException 設定ファイルのXMLファイル解析が失敗した時に発生します。
	 */
	public void initialize(String key)
			throws ConfigFileNotFoundException, ConfigFileDefinitionException,
			ConfigFileIOException, ConfigFileParseException {

		// keyよりデータアクセス設定ファイルを読み込む
		DBAccessConfig info = (DBAccessConfig) ConfigurationLoader
							.load(RULE_FILE, key);

		// キャッシュする
		for (SelectSql sql : info.getSelectSql()) {
			// クエリー用SQLをキャッシュする
			selectSqlCache.put(sql.getId(), sql.getSelect());
		}

		for (UpdateSql sql : info.getUpdateSql()) {
			// 分割コミット単位をキャッシュする
			commitLimitCountMap.put(sql.getId(), sql.getCommitLimitCount());
			// 更新用SQLをキャッシュする
			updateSqlCache.put(sql.getId(), sql.getUpdate());
		}

		for (InsertSql sql : info.getInsertSql()) {
			// 分割コミット単位をキャッシュする
			commitLimitCountMap.put(sql.getId(), sql.getCommitLimitCount());
			//インサート用SQLをキャッシュする
			insertSqlCache.put(sql.getId(),sql.getInsert());
		}

		for (DeleteSql sql : info.getDeleteSql()) {
			// 分割コミット単位をキャッシュする
			commitLimitCountMap.put(sql.getId(), sql.getCommitLimitCount());
			// 削除用SQLをキャッシュする
			deleteSqlCache.put(sql.getId(), sql.getDelete());
		}
	}

	/**
	 *
	 * 後処理する。
	 *
	 */
	public void release() {

		// resultSet、ステートメント、
		// コネクションをクローズする
		getCB3JdbcTemplate().close();
		// キャッシュをクリアする
		selectSqlCache.clear();
		updateSqlCache.clear();
		insertSqlCache.clear();
		deleteSqlCache.clear();
		commitLimitCountMap.clear();
		commitCountMap.clear();
	}

	/**
	 * {@inheritDoc}
	 */
	public DBRecordReader select(String key, DBQueryParameterMap parameter)
			throws DataFileNoSuchSqlIdException, DataAccessException {

		// SQL文を取得する
		String sql = selectSqlCache.get(key);
		// SQL文がNULL場合
		if (sql == null || "".equals(sql)) {
			// DataFileNoSuchSqlIdExceptionを作成する
			DataFileNoSuchSqlIdException ex = new DataFileNoSuchSqlIdException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_DBACCESS_006,
							LogMessageConstant.CB3_ACCESSOR_DBACCESS_011, key, "select"));
			//ログ出力
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}

		// パラメータを付けない場合、クエリーを実行する
		ResultSet rs = getCB3JdbcTemplate().query(sql, parameter);

		// 結果を設定する
		JdbcRecordReader recordReader = new JdbcRecordReader(rs, sql, parameter);
		// 結果を戻す
		return recordReader;
	}

	/**
	 * {@inheritDoc}
	 */
	public DBRecordReader select(String key)
		throws DataFileNoSuchSqlIdException, DataAccessException {

		// SQL文を取得する
		String sql = selectSqlCache.get(key);
		// SQL文がNULL場合
		if (sql == null || "".equals(sql)) {
			// DataFileNoSuchSqlIdExceptionを作成する
			DataFileNoSuchSqlIdException ex = new DataFileNoSuchSqlIdException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_DBACCESS_007,
							LogMessageConstant.CB3_ACCESSOR_DBACCESS_011, key, "select"));
			//ログ出力
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}

		// パラメータを付けない場合
		ResultSet rs = getCB3JdbcTemplate().query(sql);

		// 結果を設定する
		JdbcRecordReader recordReader = new JdbcRecordReader(rs, sql);
		// 結果を戻す
		return recordReader;
	}

	/**
	 * {@inheritDoc}
	 */
	public int update(String key, DBQueryParameterMap parameter)
		throws DataFileNoSuchSqlIdException, DataAccessException, AccessorUnknownException {

		// SQL文を取得する
		String sql = updateSqlCache.get(key);
		// SQL文がNULL場合
		if (sql == null || "".equals(sql)) {
			// DataFileNoSuchSqlIdExceptionを作成する
			DataFileNoSuchSqlIdException ex = new DataFileNoSuchSqlIdException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_DBACCESS_008,
							LogMessageConstant.CB3_ACCESSOR_DBACCESS_011, key, "update"));
			//ログ出力
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}

		try {
			// パラメータを付ける場合
			int rtCnt = getCB3JdbcTemplate().update(sql, parameter);
			// 分割コミット
			divideCommit(key,rtCnt);

			// 結果を戻す
			return rtCnt;

		} catch (DataAccessException e) {
			// DataAccessExceptionの場合はそのままスローします。
			throw e;

		} catch (Exception e) {
			// その他エラーが発生した場合、AccessorUnknownExceptionを作成する
			Object[] msg = {
					sql,
					parameter
				};
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_DBACCESS_009,
					LogMessageConstant.CB3_ACCESSOR_DBACCESS_009, msg), e);
			// infoでログを出力する
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public int update(String key)
		throws DataFileNoSuchSqlIdException, DataAccessException, AccessorUnknownException {

		// SQL文を取得する
		String sql = updateSqlCache.get(key);
		// SQL文がNULL場合
		if (sql == null || "".equals(sql)) {
			// DataFileNoSuchSqlIdExceptionを作成する
			DataFileNoSuchSqlIdException ex = new DataFileNoSuchSqlIdException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_DBACCESS_010,
							LogMessageConstant.CB3_ACCESSOR_DBACCESS_011, key, "update"));
			//ログ出力
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}

		try {
			// パラメータを付けない場合
			int rtCnt = getCB3JdbcTemplate().update(sql);
			// 分割コミット
			divideCommit(key,rtCnt);

			// 結果を戻す
			return	rtCnt;

		} catch (DataAccessException e) {
			// DataAccessExceptionの場合はそのままスローします。
			throw e;
		} catch (Exception e) {
			// その他エラーが発生した場合、AccessorUnknownExceptionをスローする
			Object[] msg = {
					sql,
					""
				};
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_DBACCESS_011,
					LogMessageConstant.CB3_ACCESSOR_DBACCESS_009, msg), e);
			// infoでログを出力する
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public int delete(String key, DBQueryParameterMap parameter)
		throws DataFileNoSuchSqlIdException, DataAccessException, AccessorUnknownException {

		// SQL文を取得する
		String sql = deleteSqlCache.get(key);
		// SQL文がNULL場合
		if (sql == null || "".equals(sql)) {
			// DataFileNoSuchSqlIdExceptionを作成する
			DataFileNoSuchSqlIdException ex = new DataFileNoSuchSqlIdException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_DBACCESS_012,
							LogMessageConstant.CB3_ACCESSOR_DBACCESS_011, key, "delete"));
			//ログ出力
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}

		try {
			// パラメータを付ける場合
			int rtCnt = getCB3JdbcTemplate().update(sql, parameter);
			// 分割コミット
			divideCommit(key,rtCnt);
			// 結果を戻す
			return	rtCnt;

		} catch (DataAccessException e) {
			// DataAccessExceptionの場合はそのままスローします。
			throw e;

		} catch (Exception e) {
			// その他エラーが発生した場合、AccessorUnknownExceptionをスローする
			Object[] msg = {
					sql,
					parameter
				};
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_DBACCESS_013,
					LogMessageConstant.CB3_ACCESSOR_DBACCESS_010, msg), e);
			// infoでログを出力する
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public int delete(String key)
		throws DataFileNoSuchSqlIdException, DataAccessException, AccessorUnknownException {

		// SQL文を取得する
		String sql = deleteSqlCache.get(key);
		// SQL文がNULL場合
		if (sql == null || "".equals(sql)) {
			// DataFileNoSuchSqlIdExceptionを作成する
			DataFileNoSuchSqlIdException ex = new DataFileNoSuchSqlIdException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_DBACCESS_014,
							LogMessageConstant.CB3_ACCESSOR_DBACCESS_011, key, "delete"));
			//ログ出力
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}

		try {
			// パラメータを付けない場合
			int rtCnt = getCB3JdbcTemplate().update(sql);
			// 分割コミット
			divideCommit(key,rtCnt);

			// 結果を戻す
			return	rtCnt;

		} catch (DataAccessException e) {
			// DataAccessExceptionの場合はそのままスローします。
			throw e;

		} catch (Exception e) {
			// その他エラーが発生した場合、AccessorUnknownExceptionをスローする
			Object[] msg = {
					sql,
					""
				};
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_DBACCESS_015,
					LogMessageConstant.CB3_ACCESSOR_DBACCESS_010, msg), e);
			// infoでログを出力する
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public int insert(String key, DBQueryParameterMap parameter)
		throws DataFileNoSuchSqlIdException, DataAccessException, AccessorUnknownException {

		// SQL文を取得する
		String sql = insertSqlCache.get(key);
		// SQL文がNULL場合
		if (sql == null || "".equals(sql)) {
			// DataFileNoSuchSqlIdExceptionを作成する
			DataFileNoSuchSqlIdException ex = new DataFileNoSuchSqlIdException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_DBACCESS_016,
							LogMessageConstant.CB3_ACCESSOR_DBACCESS_011, key, "insert"));
			//ログ出力
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}

		try {
			// パラメータを付ける場合
			int rtCnt = getCB3JdbcTemplate().update(sql, parameter);
			// 分割コミット
			divideCommit(key,rtCnt);

			// 結果を戻す
			return	rtCnt;

		} catch (DataAccessException e) {
			// DataAccessExceptionの場合はそのままスローします。
			throw e;

		} catch (Exception e) {
			// その他エラーが発生した場合、AccessorUnknownExceptionをスローする
			Object[] msg = {
					sql,
					parameter
				};
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_DBACCESS_017,
					LogMessageConstant.CB3_ACCESSOR_DBACCESS_008, msg), e);
			// infoでログを出力する
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public int insert(String key)
		throws DataFileNoSuchSqlIdException, DataAccessException, AccessorUnknownException {

		// SQL文を取得する
		String sql = insertSqlCache.get(key);
		// SQL文がNULL場合
		if (sql == null || "".equals(sql)) {
			// DataFileNoSuchSqlIdExceptionを作成する
			DataFileNoSuchSqlIdException ex = new DataFileNoSuchSqlIdException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_DBACCESS_018,
							LogMessageConstant.CB3_ACCESSOR_DBACCESS_011, key, "insert"));
			//ログ出力
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}

		try {
			// パラメータを付けない場合
			int rtCnt = getCB3JdbcTemplate().update(sql);
			divideCommit(key,rtCnt);

			// 結果を戻す
			return rtCnt;

		} catch (DataAccessException e) {
			// DataAccessExceptionの場合はそのままスローします。
			throw e;

		} catch (Exception e) {
			// その他エラーが発生した場合、AccessorUnknownExceptionをスローする
			Object[] msg = {
					sql,
					""
				};
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_DBACCESS_019,
					LogMessageConstant.CB3_ACCESSOR_DBACCESS_008, msg), e);
			// infoでログを出力する
			LOG.info(ex.getMessage(), ex);
			// スローする
			throw ex;
		}
	}

	/**
	 *
	 * 指定されたSQLに分割コミットの指定があり、指定件数に達した場合はコミットを行う。
	 *
	 * @param key SQL文ID
	 * @param count 分割コミットを行う件数
	 */
	private void divideCommit(String key, int count) {

		// 分割コミット単位
		String cmtLimitCnt = commitLimitCountMap.get(key);
		// 件数カウント
		Integer cmtCount = commitCountMap.get(key);

		if (cmtCount == null) {
			cmtCount = 0;
		}

		// コミット単位を指定いる場合
		if (cmtLimitCnt != null && !"".equals(cmtLimitCnt)){
			// 件数カウント
			cmtCount += count;
			// コミット単位件数を超える場合
			if(cmtCount >= Integer.parseInt(cmtLimitCnt)){
				// 分割コミット
				getCB3JdbcTemplate().commit();
				// カウントをクリアする
				cmtCount = 0;
			}
			commitCountMap.put(key, cmtCount);
		}
	}
}

