/*
 * MosP - Mind Open Source Project    http://www.mosp.jp/
 * Copyright (C) MIND Co., Ltd.       http://www.e-mind.co.jp/
 * 
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jp.mosp.setup.bean;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import jp.mosp.framework.base.BaseBean;
import jp.mosp.framework.base.DBConnBean;
import jp.mosp.framework.base.MospException;
import jp.mosp.framework.base.MospParams;
import jp.mosp.framework.constant.MospConst;
import jp.mosp.framework.property.MospProperties;
import jp.mosp.platform.constant.PlatformMessageConst;
import jp.mosp.setup.dao.SetupDao;

/**
 * SetupシステムのBean。<br>
 * DBの接続の確認を行う。<br>
 */

public class DbCreateBean extends BaseBean {
	
	private SetupDao			dao;
	
	/**
	 * 接続失敗時のメッセージコード。<br>
	 */
	public static final String	MSG_CONECTION		= "SUE001";
	
	/**
	 * SQL文を流す時のメッセージコード。<br>
	 */
	public static final String	MSG_SQL				= "SUE002";
	
	/**
	 * DB作成失敗時のメッセージコード。<br>
	 */
	public static final String	MSG_CREATEDB		= "SUE003";
	
	/**
	 * DB作成失敗時のメッセージコード。<br>
	 */
	public static final String	MSG_CREATEROLE		= "SUE004";
	
	/**
	 * 既に存在時のメッセージコード。<br>
	 */
	public static final String	MSG_YET				= "SUE005";
	
	/**
	 * SQLファイルのエンコーディング指定。<br>
	 */
	public static final String	ENCODE_SQL_FILE		= "UTF-8";
	
	/**
	 * SQLファイルディレレクトリーパス。<br>
	 */
	public static final String	PATH_SQL_DIR		= "/sql/";
	
	/**
	 * SQLファイルの拡張子。<br>
	 */
	public static final String	SUFFIX_SQL_FILE		= ".sql";
	
	/**
	 * GRANTSQLファイルに含まれる文字。<br>
	 */
	public static final String	FILE_GRANT			= "grant";
	
	/**
	 * GRANT文に元々あるロール名。<br>
	 */
	public static final String	DEFAULT_ROLL_NAME	= "usermosp";
	
	/**
	 * DB接続設定XMLファイル。<br>
	 */
	public static final String	PATH_XML_FILE		= "/WEB-INF/xml/user/user_connection.xml";
	
	/**
	 * PostgresSQL文字列。<br>
	 */
	public static final String	NAME_POSTGRES		= "postgres";
	

	/**
	 * mospParamsにセットする。<br>
	 * @param mospParams MosP処理情報
	 */
	public void setMospParams(MospParams mospParams) {
		this.mospParams = mospParams;
	}
	
	/**
	 * DAOにコネクションを入れる。<br>
	 */
	@Override
	public void initBean() throws MospException {
		// DAO準備インスタンス代入
		dao = (SetupDao)createDao(SetupDao.class);
		// mospParamsとdbConnBeanさんから持ってきたコネクトを入れる
		dao.setInitParams(mospParams, connection);
	}
	
	/**
	 * DB接続を取得する。<br>
	 * @param driver JDBCドライバー
	 * @param url postgresURL
	 * @param user Postgresユーザ
	 * @param pass Postgresパスワード
	 * @throws MospException 接続できなかった場合
	 */
	public void createConectionn(String driver, String url, String user, String pass) throws MospException {
		try {
			// ドライバクラスのロード 
			Class.forName(driver);
			// Connectionの作成 
			connection = DriverManager.getConnection(url, user, pass);
		} catch (Throwable t) {
			throw new MospException(t);
		}
	}
	
	/**
	 * DB接続を取得する。<br>
	 * @param serverName サーバ名
	 * @param port ポート番号
	 * @param dbName データベース名
	 * @param user Postgresユーザ
	 * @param pass Postgresパスワード
	 * @throws MospException 接続の取得が失敗した場合
	 */
	public void createConectionn(String serverName, String port, String dbName, String user, String pass)
			throws MospException {
		
		// driverの取得
		MospProperties properties = mospParams.getProperties();
		String driver = properties.getApplicationProperty(DBConnBean.APP_DB_DRIVER);
		// URLの生成
		String url = getDbUrl(serverName, port, dbName);
		try {
			// DB接続
			createConectionn(driver, url, user, pass);
		} catch (Throwable t) {
			mospParams.addErrorMessage(DbCreateBean.MSG_CONECTION, null);
		}
	}
	
	/**
	 * DBに接続するためのURLを取得する。<br>
	 * @param serverName サーバ名
	 * @param port ポート番号
	 * @param dbName データベース名
	 * @return URL
	 */
	public String getDbUrl(String serverName, String port, String dbName) {
		return "jdbc:postgresql://" + serverName + ":" + port + "/" + dbName;
	}
	
	/**
	 * DBが作成されているかを確認する。
	 * 現在のXMLからDB接続情報を取得し、接続を試みる。
	 * @return 確認結果(true：DBが作成されている場合、false：DBが作成されていない場合)
	 * @throws MospException DB操作に失敗した場合
	 */
	public boolean confirmDbCreation() throws MospException {
		// 今のXMLを読み込む
		MospProperties properties = mospParams.getProperties();
		String driver = properties.getApplicationProperty(DBConnBean.APP_DB_DRIVER);
		String url = properties.getApplicationProperty(DBConnBean.APP_DB_URL);
		String user = properties.getApplicationProperty(DBConnBean.APP_DB_USER);
		String pass = properties.getApplicationProperty(DBConnBean.APP_DB_PASS);
		try {
			// 接続する
			createConectionn(driver, url, user, pass);
		} catch (MospException e) {
			return false;
		} finally {
			// コネクション開放
			connEnd();
		}
		return true;
	}
	
	/**
	 * データベース・ロールが作成できるかを確認する。<br>
	 * 対象データベース及び対象ロールが存在しなければ、作成可能とする。<br>
	 * @param dbName   対象データベース名
	 * @param roleName 対象ロール名
	 * @return 確認結果(true：作成可能、false：作成不能)
	 * @throws MospException SQLの作成に失敗した場合、或いはSQL例外が発生した場合
	 */
	public boolean checkCreateDb(String dbName, String roleName) throws MospException {
		// 対象データベース及び対象ロールが存在しなければtrue
		return dao.isDatabaseExist(dbName) == false && dao.isRoleExist(roleName) == false;
	}
	
	/**
	 * DBを作成する。<br>
	 * @param dbName DB名 
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合
	 */
	public void createDb(String dbName) throws MospException {
		// createDBでDB作成
		dao.createDb(dbName);
	}
	
	/**
	 * ロールを作成する。<br>
	 * @param roleName ロール名
	 * @param rolePw ロールパスワード
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合
	 */
	public void createRole(String roleName, String rolePw) throws MospException {
		// createRoleでロール作成
		dao.createRole(roleName, rolePw);
	}
	
	/**
	 * SQLファイルを読み込む。<br>
	 * @param filePath SQLファイルパス
	 * @return SQLファイル内容
	 * @throws MospException SQLファイル読み込みに失敗した場合
	 */
	public String readSqlFile(String filePath) throws MospException {
		// ファイル読み込みに使用する宣言
		InputStreamReader inputStreamReader = null;
		FileInputStream fileInputStream = null;
		BufferedReader br = null;
		try {
			// Pathをfileにつめる
			File file = new File(filePath);
			// ファイルをjavaで開けるようにする
			fileInputStream = new FileInputStream(file);
			// ファイルをjavaで読めるようにする
			inputStreamReader = new InputStreamReader(fileInputStream, ENCODE_SQL_FILE);
			// 一行ずつ読む
			br = new BufferedReader(inputStreamReader);
			// String変数の宣言
			String str;
			// 初期化
			StringBuffer sb = new StringBuffer();
			// 最後まで繰り返す（String変数に代入）
			while ((str = br.readLine()) != null) {
				sb.append(str);
			}
			return sb.toString();
		} catch (Throwable t) {
			throw new MospException(t);
		} finally {
			// 全て開放
			try {
				if (br != null) {
					br.close();
				}
				if (inputStreamReader != null) {
					inputStreamReader.close();
				}
				if (fileInputStream != null) {
					fileInputStream.close();
				}
			} catch (Throwable t) {
				t.printStackTrace();
				throw new MospException(t);
			}
		}
	}
	
	/**
	 * SQLフォルダの中のSQLファイルを閲覧してSQLを実行する。<br>
	 * @param roleName ロール名
	 * @throws MospException SQL実行に失敗した場合
	 */
	public void createTable(String roleName) throws MospException {
		// GRANTSQLファイルリスト準備
		List<String> grantList = new ArrayList<String>();
		// SQLディレクトリーを取得
		File dir = new File(mospParams.getApplicationProperty(MospConst.APP_DOCBASE) + PATH_SQL_DIR);
		// ファイルごとに処理
		for (File file : dir.listFiles()) {
			// ファイル確認
			if (file.isFile() == false) {
				continue;
			}
			// ファイル名取得
			String fileName = file.getName();
			// 拡張子確認
			if (fileName.endsWith(SUFFIX_SQL_FILE) == false) {
				continue;
			}
			// GRANTSQLファイルを除外
			if (fileName.contains(FILE_GRANT)) {
				grantList.add(file.getPath());
				continue;
			}
			// SQLファイルを読み込む
			String query = readSqlFile(file.getPath());
			// SQLファイルを実行
			dao.executeUpdate(query);
			
		}
		// GRANT文の実行
		for (String filePath : grantList) {
			// SQLファイルを読み込む
			String query = readSqlFile(filePath);
			// ROLE名を自分が決めたロール名に置換する
			query = query.replaceAll(DEFAULT_ROLL_NAME, roleName);
			// SQLファイルを実行
			dao.executeUpdate(query);
		}
		
	}
	
	/**
	 * 設定ファイルuserConnection.xml 作成。<br>
	 * @param serverName サーバ名
	 * @param port ポート番号
	 * @param dbName DB名
	 * @param roleName ロール名
	 * @param rolePass ロールパスワード
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合 
	 */
	public void createXml(String serverName, String port, String dbName, String roleName, String rolePass)
			throws MospException {
		// 作成場所を指定
		String path = mospParams.getApplicationProperty(MospConst.APP_DOCBASE) + PATH_XML_FILE;
		// ファイルを初期化
		File file = new File(path);
		try {
			// ファイル作成
			file.createNewFile();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		if (checkBeforeWritefile(file)) {
			PrintWriter pw;
			try {
				// 文字コードをUTF-8に指定
				pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), ENCODE_SQL_FILE));
				// 文章を書き込む
				pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
				pw.println("<!DOCTYPE MosP>");
				pw.println("<MosP>");
				pw.println("<Application key=\"DbUrl\">");
				pw.println(getDbUrl(serverName, port, dbName));
				pw.println("</Application>");
				pw.println("<Application key=\"DbUser\">");
				pw.println(roleName);
				pw.println("</Application>");
				pw.println("<Application key=\"DbPass\">");
				pw.println(rolePass);
				pw.println("</Application>");
				pw.println("</MosP>");
				// 書き込み終了
				pw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			// 作成に失敗するとメッセージを返す
			mospParams.addErrorMessage(PlatformMessageConst.MSG_REG_DUPLICATE, null);
		}
		
	}
	
	/**
	 * XML設定ファイル作成チェック。<br>
	 * 作成したファイルが存在するか確認。<br>
	 * @param file XML設定ファイル
	 * @return 確認結果(true：作成成功、false：作成失敗)
	 */
	private static boolean checkBeforeWritefile(File file) {
		if (file.exists()) {
			// ファイルが作成できているか確認
			if (file.isFile() && file.canWrite()) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * アプリケーション設定のDB名変更。<br>
	 * @param serverName サーバ名
	 * @param port ポート番号
	 * @param dbName DB名
	 */
	public void setApplicationUrl(String serverName, String port, String dbName) {
		MospProperties properties = mospParams.getProperties();
		// アプリケーションのDB名の設定を上書き
		properties.setApplicationProperty(DBConnBean.APP_DB_URL, getDbUrl(serverName, port, dbName));
	}
	
	/**
	 * アプリケーション設定のロールユーザ名変更。<br>
	 * @param roleName  ロール名
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合
	 */
	public void setApplicationUser(String roleName) throws MospException {
		MospProperties properties = mospParams.getProperties();
		// アプリケーションのロールユーザの設定を上書き
		properties.setApplicationProperty(DBConnBean.APP_DB_USER, roleName);
	}
	
	/**
	 * アプリケーション設定のロールパスワード変更。<br>
	 * @param rolePw ロールパスワード
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合
	 */
	public void setApplicationPass(String rolePw) throws MospException {
		MospProperties properties = mospParams.getProperties();
		// アプリケーションのロールパスワードの設定を上書き
		properties.setApplicationProperty(DBConnBean.APP_DB_PASS, rolePw);
	}
	
	/**
	 * DB接続終了メソッド。<br>
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合
	 */
	public void connEnd() throws MospException {
		if (connection != null) {
			try {
				// コネクション開放
				connection.close();
			} catch (SQLException e) {
				throw new MospException(e);
			}
		}
	}
}
