/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/

package jp.co.ntt.lms.installer.lms;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import jp.co.ntt.lms.installer.core.InstallerAction;
import jp.co.ntt.lms.installer.core.InstallerActionValue;
import jp.co.ntt.lms.installer.core.InstallerException;
import jp.co.ntt.lms.installer.core.InstallerValues;
import jp.co.ntt.lms.installer.core.util.InstallerDatabaseUtil;
import jp.co.ntt.lms.installer.core.util.InstallerFileUtil;
import jp.co.ntt.lms.installer.core.util.InstallerUiUtil;
import jp.co.ntt.lms.installer.util.LoggerUtil;
import jp.co.ntt.lms.installer.util.StringUtil;

/**
 * データベース・テーブルを作成するアクション・クラス。
 * @author T.Nishiki
 */
public abstract class CreateDbAbstractAction implements InstallerAction {
	/**
	 * ログ出力オブジェクト。
	 */
	private static Logger _logger;

	/**
	 * スタティック・イニシャライザ。
	 */
	static {
		_logger = LoggerUtil.getLogger(CreateDbAbstractAction.class);
	}

	/**
	 * データベース管理者用コネクション。
	 */
	private Connection _dbaConection;

	/**
	 * LMS運用用コネクション。
	 */
	private Connection _lmsConection;

	/**
	 * アクションをスキップするか判定します。<br>
	 * ユーザからデータベースの初期化すること確認しており、
	 * データベースの種別が指定の種別と一致する場合のみ、falseを戻します。<br>
	 *
	 * @param values インストーラ共通データ・オブジェクト。
	 * @return 上記参照。
	 */
	public final boolean isSkipped(InstallerValues values) {
		// ユーザから初期化の確認が取れていること
		ConfirmDbInitializeValue dbInitializeValue
			= (ConfirmDbInitializeValue)values.getActionValue(
					ConfirmDbInitializeAction.class.getName());

		return (dbInitializeValue.isConfirmInitialize() == false)
			|| (values.getDbCategory().equals(getDbCategory()) == false);
	}

	/**
	 * データベース・テーブルを作成する共通アクション。
	 * @param values インストーラ共通データ・オブジェクト。
	 * @return nullを戻します。
	 * @throws InstallerException データベースアクセス中に例外が発生した場合。
	 */
	public InstallerActionValue action(InstallerValues values)
		throws InstallerException {

		LoggerUtil.methodHead(_logger, "action(InstallerValues)");
		boolean successFlag = false;
		try {
			// 開始メッセージ出力
			InstallerUiUtil.outputMessageAsTitle(
				values,	"CreateDbAbstractAction.head");
			// =================================================================
			// ユーザ・データベース作成
			createUserAndDatabase(values);
 			// =================================================================
			// 通常のクエリ（前半）
			Connection connection = getLmsUserConnection(values);
			executeQueryType(values, connection, "common.first");
			// =================================================================
			// オプションLO用クエリ
			executeQueriesOptionLO(values, connection);
			// =================================================================
			// 通常のクエリ（後半）
			executeQueryType(values, connection, "common.second");
			// コミット
			if (_lmsConection != null
					&& _lmsConection.getAutoCommit() == false) {
				_lmsConection.commit();
			}
			if (_dbaConection != null
					&& _dbaConection.getAutoCommit() == false) {
				_dbaConection.commit();
			}
			// tryブロック末端まで到達
			successFlag = true;
			// 終了メッセージ出力
			InstallerUiUtil.outputMessage(
				values, "CreateDbAbstractAction.tail", true);
		}
		catch (InstallerException e) {
			// エラーメッセージ出力
			InstallerUiUtil.outputMessage(
				values, "CreateDbAbstractAction.error", true);
			rollback();
			throw e;
		}
		catch (Exception e) {
			// エラーメッセージ出力
			InstallerUiUtil.outputMessage(
				values, "CreateDbAbstractAction.error", true);
			rollback();
			throw new InstallerException(e);
		}
		finally {
			closeConnection(successFlag);
		}
		LoggerUtil.methodTail(_logger, "action(InstallerValues)");
		return null;
	}

	/**
	 * 対応するデータベースの種別を戻します。<br>
	 * @return データベース種別を戻します。
	 */
	protected abstract String getDbCategory();


	/**
	 * ユーザとデータベース作成処理。<br>
	 * @param values インストーラ共通データ・オブジェクト。
	 * @throws InstallerException データベースアクセス中に例外が発生した場合。
	 */
	protected void createUserAndDatabase(InstallerValues values)
		throws InstallerException {

		Connection connection = getDbaConnection(values);
		executeQueryType(values, connection, "create.database");
		executeQueryType(values, connection, "create.user");
	}

	/**
	 * 渡されたクエリー文が無視してよいか判定します。
	 * @param queryLine 判定対象のクエリー一文。
	 * @return 無視する場合true。
	 */
	protected boolean isIgnoreQuery(String queryLine) {
		return false;
	}

	/**
	 * 指定されたクエリタイプのクエリーを実行します。<br>
	 *
	 * @param values インストーラ共通データ・オブジェクト。
	 * @param connection クエリ文実行するためのデータベース接続オブジェクト。
	 * @param queryType 実行するクエリー種別。
	 * @throws InstallerException データベースアクセス中に例外が発生した場合。
	 */
	protected final void executeQueryType(
			InstallerValues values, Connection connection, String queryType)
			throws InstallerException {
		// メソッド開始ログ
		LoggerUtil.methodHead(_logger,
			"executeQueryType(InstallerValues,"
				+ connection + "," + queryType + ")");

		Statement statement = null;
		boolean successFlag = false;
		try {
			String[][] queryResources
				= values.getQueryResources(queryType);
			// クエリ分がない場合は終了
			if (queryResources.length > 0) {
				for (int i = 0; i < queryResources.length; i++) {
					// クエリ実行
					String[] queries = loadQuery(values,
						queryResources[i][0], queryResources[i][1]);
					// クエリ実行
					_logger.info("[" + i + "]"
						+ "[" + StringUtil.toString(queries) + "]");
					// ステートメント取得・実行
					for (int j = 0; j < queries.length; j++) {
						statement = connection.createStatement();
						statement.execute(queries[j]);
					}
					// ステートメントクローズ
					InstallerDatabaseUtil.close(statement, true);
				}
				// トランザクション・コミット
				if (connection.getAutoCommit() == false) {
					connection.commit();
				}
			}
			// tryブロック末端まで到達
			successFlag = true;
		}
		catch (Exception e) {
			LoggerUtil.throwable(_logger, e);
			throw new InstallerException(e);
		}
		finally {
			InstallerDatabaseUtil.close(statement, successFlag);
		}
		// メソッド終了ログ
		LoggerUtil.methodTail(_logger,
			"executeQueryType(InstallerValues,"
				+ connection + "," + queryType + ")");
	}

	/**
	 * オプションLO関連のクエリー実行します。<br>
	 * @param values インストーラ共通データ・オブジェクト。
	 * @param connection データベースコネクションオブジェクト。
	 * @throws InstallerException データベースアクセス中に例外が発生した場合。
	 */
	protected void executeQueriesOptionLO(
		InstallerValues values, Connection connection)
		throws InstallerException {
		// メソッド開始ログ
		LoggerUtil.methodHead(_logger,
			"executeQueriesOptionLO(InstallerValues)");
		String[] optionLOKeys = values.getOptionLOKeys();
		for (int i = 0; i < optionLOKeys.length; i++) {
			if ( values.getOptionLOSelected(optionLOKeys[i]) == false ) {
				continue;
			}
			executeQueryType(values, connection,
				"optionLO." + optionLOKeys[i]);
		}
		// メソッド終了ログ
		LoggerUtil.methodTail(_logger,
			"executeQueriesOptionLO(InstallerValues)");
	}

	/**
	 * データベース管理者がアクセスするデータベース名を取得します。
	 * このクラスではInstallerValues#getDbName()の戻り値を戻しています。
	 * @param values インストーラ共通データ・オブジェクト。
	 * @return データベース管理者がアクセスするデータベース名。
	 * @see InstallerValues#getDbName()
	 */
	protected String getDbNameForDba(InstallerValues values) {
		// データベース情報は、原則マネージャの設定値の入力
		ManagerInputValue managerValue
			= (ManagerInputValue)values.getActionValue(
					ManagerInputAction.class.getName());
		return managerValue.getDbName();
	}

	/**
	 * データベース管理者権限でのコネクションを取得します。
	 * @param values インストーラ共通データ・オブジェクト。
	 * @return データベース管理者権限でのコネクション。
	 * @throws InstallerException コネクション取得に失敗した場合。
	 */
	protected Connection getDbaConnection(
		InstallerValues values)
		throws InstallerException {

		LoggerUtil.methodHead(_logger,
			"getDbaConnection(InstallerValues)");
		try {
			if (_dbaConection != null && _dbaConection.isClosed() == false) {
				if (_dbaConection.getAutoCommit() == false) {
					_dbaConection.commit();
				}
				_dbaConection.close();
				_dbaConection = null;
			}
			// データベース情報は、原則マネージャの設定値の入力
			ManagerInputValue managerValue
				= (ManagerInputValue)values.getActionValue(
						ManagerInputAction.class.getName());
			// ユーザから初期化の際に入力した値も使用する。
			ConfirmDbInitializeValue dbInitializeValue
				= (ConfirmDbInitializeValue)values.getActionValue(
						ConfirmDbInitializeAction.class.getName());
			// DBホスト
			String host = managerValue.getDbHost();
			// DBポート番号
			String port = managerValue.getDbPort();
			// データベース名
			String dbName = getDbNameForDba(values);
			// ユーザ名
			String user = dbInitializeValue.getDbaUser();
			// パスワード
			String pass = dbInitializeValue.getDbaPassword();
			// コネクション生成
			_dbaConection = InstallerDatabaseUtil.getConnection(
				values, host, port, dbName, user, pass,
				"connectionStringPatternDba", true);
		}
		catch (InstallerException e) {
			throw e;
		}
		catch (Exception e) {
			LoggerUtil.throwable(_logger, e);
			throw new InstallerException(e);
		}
		LoggerUtil.methodTail(_logger,
			"getDbaConnection(InstallerValues)");
		return _dbaConection;
	}

	/**
	 * LMS運用時のユーザでのコネクションを取得します。
	 * @param values インストーラ共通データ・オブジェクト。
	 * @return LMS運用時のユーザでのコネクション。
	 * @throws InstallerException コネクション取得に失敗した場合。
	 */
	protected Connection getLmsUserConnection(
		InstallerValues values)
		throws InstallerException {

		LoggerUtil.methodHead(_logger,
			"getLmsUserConnection(InstallerValues)");
		try {
			if (_lmsConection != null && _lmsConection.isClosed() == false) {
				if (_lmsConection.getAutoCommit() == false) {
					_lmsConection.commit();
				}
				_lmsConection.close();
				_lmsConection = null;
			}
			// データベース情報は、原則マネージャの設定値の入力
			ManagerInputValue managerValue
				= (ManagerInputValue)values.getActionValue(
						ManagerInputAction.class.getName());
			String host = managerValue.getDbHost();
			String port = managerValue.getDbPort();
			String dbName = managerValue.getDbName();
			String user = managerValue.getDbUser();
			String pass = managerValue.getDbPassword();
			_lmsConection = InstallerDatabaseUtil.getConnection(
				values, host, port, dbName, user, pass,
				"connectionStringPattern", true);
		}
		catch (InstallerException e) {
			throw e;
		}
		catch (Exception e) {
			LoggerUtil.throwable(_logger, e);
			throw new InstallerException(e);
		}
		LoggerUtil.methodTail(_logger,
			"getLmsUserConnection(InstallerValues)");
		return _lmsConection;
	}

	/**
	 * コネクションをロールバックします。<br>
	 * ロールバックで例外が発生しても、他のエラーが発生して
	 * ロールバックをしているはずなので、例外はスローしません。
	 */
	private void rollback() {
		// 管理者コネクションのロールバック
		if (_dbaConection != null) {
			try {
				if (_dbaConection.getAutoCommit() == false) {
					_dbaConection.rollback();
				}
			}
			catch (Exception e) {
				LoggerUtil.throwable(_logger, e);
			}
		}
		// 運用用コネクションのロールバック
		if (_lmsConection != null) {
			try {
				if (_lmsConection.getAutoCommit() == false) {
					_lmsConection.rollback();
				}
			}
			catch (Exception e) {
				LoggerUtil.throwable(_logger, e);
			}
		}
	}

	/**
	 * コネクションをクローズします。<br>
	 * @param throwFlag コネクションをクローズに失敗したときに
	 *                   例外をスローする場合は、true。
	 * @throws InstallerException 上記参照。
	 */
	private void closeConnection(boolean throwFlag)
		throws InstallerException {
		InstallerException thrownException = null;

		// 管理者コネクションのクローズ
		if (_dbaConection != null) {
			try {
				_dbaConection.close();
			}
			catch (Exception e) {
				LoggerUtil.throwable(_logger, e);
				if (throwFlag) {
					thrownException = new InstallerException(e);
				}
			}
			_dbaConection = null;
		}
		// 運用用コネクションのクローズ
		if (_lmsConection != null) {
			try {
				_lmsConection.close();
			}
			catch (Exception e) {
				LoggerUtil.throwable(_logger, e);
				if (throwFlag && thrownException == null) {
					thrownException = new InstallerException(e);
				}
			}
			_lmsConection = null;
		}
		// 例外が発生した場合はスロー。
		if (thrownException != null) {
			throw thrownException;
		}
	}

	/**
	 * 指定したリソースのクエリ文字列を取得します。
	 * @param values インストーラ共通データオブジェクト。
	 * @param resourcePath クエリ文を取得するためのリソースを取得する。
	 * @param encode リソースの文字コード。
	 * @return リソースから取得したクエリー文字列。
	 * @throws InstallerException リソースからの取得に失敗したとき
	 */
	protected String[] loadQuery(
		InstallerValues values, String resourcePath, String encode)
		throws InstallerException {

		// メソッド開始ログ
		LoggerUtil.methodHead(_logger,
			"getLmsUserConnection(InstallerValues,"
				+ resourcePath + "," + encode + ")");

		// リソースからの文字入力ストリーム
		BufferedReader reader = null;
		// クエリ文字列（このメソッドの戻り値）
		String[] queries = null;
		// String
		List queryList = new ArrayList();
		// 成功フラグ（成功した場合にtrueに変更）
		boolean successFlag = false;
		String endOfLine = System.getProperty("line.separator");
		try {
			InputStream resourceStream
				= values.getResourcePathStream(resourcePath);
			if (resourceStream != null) {
				// 文字入力ストリームを取得
				reader = new BufferedReader(
					new InputStreamReader(resourceStream, encode));
				StringBuffer buffer = new StringBuffer();
				// １行ずつよみ、その都度、連結する。
				while (true) {
					String line = reader.readLine();
					if (line == null) {
						break;
					}
					if (isIgnoreQuery(line)) {
						continue;
					}
					// 置換
					line = replace(values, line);
					// セミコロンが見つかり次第分割
					int pos = line.indexOf(';');
					if (pos >= 0) {
						String forward = null;
						String back = null;
						if (pos == 0) {
							forward = "";
							back = line.substring(pos + 1);
						}
						else if (pos + 1 == line.length()) {
							forward = line.substring(0, pos);
							back = "";
							buffer.append(forward);
						}
						else {
							forward = line.substring(0, pos);
							back = line.substring(pos + 1);
							buffer.append(forward);
						}
						// セミコロンより前を連結して、リストに登録
						String query = buffer.toString().trim();
						if (query.length() > 0) {
							queryList.add(query);
						}

						// あらたなクエリー文を生成
						buffer = new StringBuffer(back);
						buffer.append(endOfLine);
					}
					else {
						buffer.append(line);
						buffer.append(endOfLine);
					}
				}

				// リストに登録していないクエリがあれば登録
				if (buffer.length() > 0) {
					String query = buffer.toString().trim();
					if (query.length() > 0) {
						queryList.add(query);
					}
				}
			}
			queries = new String[queryList.size()];
			queryList.toArray(queries);
			// tryブロック終端まで到達
			successFlag = true;
		}
		catch (Exception e) {
			LoggerUtil.throwable(_logger, e);
			throw new InstallerException(e);
		}
		finally {
			InstallerFileUtil.close(reader, successFlag);
		}
		// メソッド終了ログ
		LoggerUtil.methodTail(_logger,
			"getLmsUserConnection(InstallerValues,"
				+ resourcePath + "," + encode + ")",
			StringUtil.toString(queries));
		return queries;
	}

	/**
	 * 与えられたクエリー文の置換を行います。
	 * @param values インストーラ共通データオブジェクト。
	 * @param queryLine 変換対象のクエリー文。
	 * @return 置換後のクエリー文。
	 */
	protected String replace(InstallerValues values, String queryLine) {
		// デフォルトは変換なし文字列を戻す。
		return queryLine;
	}
}
