/*
 * Copyright (C) 2005 NTT DATA Corporation
 * 
 */
package org.postgresforest.tool.db;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.StringTokenizer;

import org.postgresforest.tool.util.StringReplaceUtil;
import org.postgresql.jdbc3.Jdbc3Connection;

/**
 * データベースアクセス基底クラス
 * 
 * GSCおよびユーザデータベースへのアクセス共通機能を提供する。
 * 
 */
public class DbBase {

	protected static final String[] protocols = { "jdbc", "postgresql" };
	protected static final String PROTOCOL = "jdbc:postgresql:";
	public static final String TEMLATE_DB = "forest_template";

	/** 作成モード定数  */
	public static final int DB_MAKE 	= 0;
	public static final int DB_ADD	= 1;
	public static final int DB_SHIFT 	= 2;

	protected Properties m_prop = new Properties(); //ユーザ・パスワード

	protected static ResourceBundle m_sqlResource = null;

	protected int m_selectServerIndex;
	
	protected ArrayList m_Connection = null; //

	protected ArrayList m_servers = null; //サーバ接続先情報リスト

	protected ArrayList m_errInfoList = new ArrayList(); //エラー情報

	{
		//JDBCドライバのロード
		try {
			Class.forName("org.postgresql.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

	}

	public DbBase() {
		m_servers = new ArrayList();
		m_Connection = new ArrayList();
		try {
			m_sqlResource = ResourceBundle.getBundle("org.postgresforest.tool.util.SQL");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * コンストラクタ
	 * @param user	ユーザ
	 * @param password　パスワード
	 */
	public DbBase(String user, String password) {
		//メンバ変数に格納
		this();
		m_prop.put("user", user);
		m_prop.put("password", password);

	}

	/**
	 * ユーザ、パスワード取得
	 * @return ユーザ、パスワードを格納したプロパティクラス
	 */
	public Properties getUserPassword() {

		return m_prop;
	}
	/**
	 * 接続情報から接続文字列の生成。
	 * @param urlinfo 接続情報
	 * @return データベース接続文字列
	 */
	protected String makeUrl(UrlInfo urlinfo) {

		return makeUrl(
			urlinfo.getHostName(),
			urlinfo.getPortNo(),
			urlinfo.getDbName());

	}
	/**
	 * 接続情報から接続文字列の生成。
	 * DB作成などのでtemplate1データベースへの接続情報を生成する
	 * @param urlinfo 接続情報
	 * @return データベース接続文字列
	 */
	protected String makeTemplateUrl(UrlInfo urlinfo) {

		return makeUrl(urlinfo.getHostName(), urlinfo.getPortNo(), TEMLATE_DB);

	}

	/**
	 * 接続文字列生成
	 * ホスト名、ポート、データベース名をDBの接続文字列に編集する
	 * @param hostName　ホスト名	
	 * @param portNo　ポート番号
	 * @param dbName　データベース名
	 * @return データベース接続文字列
	 */
	static public String makeUrl(
		String hostName,
		String portNo,
		String dbName) {

		StringBuffer serverUrlWk = new StringBuffer();
		if (!hostName.equals("")) {
			serverUrlWk.append("//");
			serverUrlWk.append(hostName);
			if (!portNo.equals("")) {
				serverUrlWk.append(":");
				serverUrlWk.append(portNo);
			}
			serverUrlWk.append("/");
		}
		serverUrlWk.append(dbName);

		return serverUrlWk.toString();
	}

	/**
	 * 接続情報設定。
	 * ホスト名、ポート、データベース名をサーんば接続先情報リストに追加する
	 * @param hostName　ホスト名	
	 * @param portNo　ポート番号
	 * @param dbName　データベース名
	 */
	public void addUrl(String hostName, String portNo, String dbName) {
		UrlInfo urlInfo = new UrlInfo();
		urlInfo.setHostName(hostName);
		urlInfo.setPortNo(portNo);
		urlInfo.setDbName(dbName);
		m_servers.add(urlInfo);
	}

	/**
	 * データベース作成処理
	 * 
	 * データベース接続先情報リストに設定された、データベースの登録を行う。
	 * データベースの作成には、template1データベースへ接続して行うこと。
	 * 
	 * @param option CREATE DATABE　オプション文字列
	 * @return	true:接続成功.false:接続失敗
	 */
	public boolean makeDB(String option, int makeMode)  {

		boolean ret = true;
		String connectUrl = "";

		//SQL取得	
		StringReplaceUtil strUtil =
			new StringReplaceUtil(m_sqlResource.getString("create.db"));
			
		//データベース追加の場合、template0を元にDBを作成する
		if (makeMode == DB_ADD) {
			option = option + " " + m_sqlResource.getString("create.db.option.dbadd");
		}
		//Ver3.1 データベース作成時にforest用templateを使用する
		else{
			option = option + " " + m_sqlResource.getString("create.db.option.default");
		}
		
		for (int i = 0; i < m_servers.size(); i++) {
			Connection con = (Connection) m_Connection.get(i);

			//サーバ接続先情報取得
			UrlInfo urlInfo = (UrlInfo) m_servers.get(i);

			try {
				connectUrl = ((Jdbc3Connection) con).getURL();

				strUtil.set(1, urlInfo.getDbName());
	
				//オプションの設定
				if (option.trim().compareTo("") != 0) {
					strUtil.set(
						2,
						m_sqlResource.getString("create.db.addoption"));
					strUtil.set(3, option);
				} else {
					strUtil.set(2, "");
					strUtil.set(3, "");
				}

				Statement ps = con.createStatement();

				
				//データベース作成処理実行
				ps.execute(strUtil.toString());

				ps.close();

			} catch (SQLException e) {
				setErrInfo(connectUrl, e);
				ret = false;
			}
		}

		return ret;

	}

	/**
	 * ユーザ作成処理
	 * 
	 * データベース接続先情報リストに設定されたサーバへユーザの作成を行う。
	 * 
	 * @param user　ユーザ名
	 * @param password	パスワード
	 * @param option	CREATE USER のオプション文字列
	 * @return	true:成功.false:失敗
	 */
	public boolean makeUser(String user, String password, String passwordOption, String option)
		{

		boolean ret = true;
		String connectUrl = "";

		//SQL取得	
		StringReplaceUtil createUserSql =
			new StringReplaceUtil(
				m_sqlResource.getString("create.user"));
				
		StringReplaceUtil alterUserSql =
			new StringReplaceUtil(
				m_sqlResource.getString("alter.user"));
					
		for (int i = 0; i < m_servers.size(); i++) {
			Connection con = (Connection) m_Connection.get(i);

			try {
				connectUrl = ((Jdbc3Connection) con).getURL();

				//ユーザーの存在をチェックする。ユーザーが存在した場合、ALTER USERを実行
				PreparedStatement psmt =
					con.prepareStatement(
						m_sqlResource.getString("check.user"));
	
				psmt.setString(1, user);
				ResultSet rs = psmt.executeQuery();
				
				int userCount = 0;
				if (rs.next()) {
					userCount = rs.getInt("user_count");
				}
				rs.close();
				psmt.close();
				
				if (userCount == 0){
					//ユーザが存在しないので作成
					
					//パラメータ設定
					createUserSql.set(1, user);
					createUserSql.set(2, passwordOption);
					createUserSql.set(3, password);
					createUserSql.set(4, " " + option);
					
					Statement smt = con.createStatement();
						
					//ユーザ作成実行
					smt.execute(createUserSql.toString());
					
					smt.close();
				}
				else{
					//ユーザが存在するので変更
					
					//パラメータ設定
					alterUserSql.set(1, user);
					alterUserSql.set(2, passwordOption);
					alterUserSql.set(3, password);
					alterUserSql.set(4, " " + option);
					
					Statement smt = con.createStatement();
						
					//ユーザ変更実行
					smt.execute(alterUserSql.toString());
					
					smt.close();
				}
			} catch (SQLException e) {
				setErrInfo(connectUrl, e);
				ret = false;
			}

		}

		return ret;

	}

	/**
	 * ユーザ削除処理
	 * 
	 * データベース接続先情報リストに設定されたサーバへユーザの削除を行う。
	 * @param user	ユーザ名
	 * @return	true:接続成功.false:接続失敗
	 */
	public boolean delUser(String user)  {


		boolean ret = true;
		String connectUrl = "";

		//SQL取得	
		StringReplaceUtil createDbSql =
			new StringReplaceUtil(m_sqlResource.getString("drop.user"));

		for (int i = 0; i < m_servers.size(); i++) {
			Connection con = (Connection) m_Connection.get(i);

			try {
				connectUrl = ((Jdbc3Connection) con).getURL();

	
				//パラメータ設定
				createDbSql.set(1, user);
	
				Statement ps = con.createStatement();
	
	
				//ユーザ削除実行	
				ps.execute(createDbSql.toString());
	
				ps.close();
			} catch (SQLException e) {
				setErrInfo(connectUrl, e);
				ret = false;
			}
		}

		return ret;


	}
	
	/**
	 * データベース削除処理
	 * 
	 * データベース接続先情報リストに設定された、データベースの削除を行う。
	 * データベースの作成には、template1データベースへ接続して行うこと。
	 * @return	true:接続成功.false:接続失敗
	 */
	public boolean delDB()  {

		boolean ret = true;
		String connectUrl = "";


		//SQL取得
		StringReplaceUtil strUtil =
			new StringReplaceUtil(m_sqlResource.getString("drop.db"));

		for (int i = 0; i < m_servers.size(); i++) {

			//サーバ接続先情報取得
			UrlInfo urlInfo = (UrlInfo) m_servers.get(i);

			Connection con = (Connection) m_Connection.get(i);

			try {
				connectUrl = ((Jdbc3Connection) con).getURL();

				//データベース名セット				
				strUtil.set(1, urlInfo.getDbName());
	
				//データベース削除実行
				Statement ps = con.createStatement();
				ps.execute(strUtil.toString());
	
				ps.close();

			
			} catch (SQLException e) {
				setErrInfo(connectUrl, e);
				ret = false;
			}
		}

		return ret;

	}

	/**
	 * コネクション接続
	 * 
	 * template1データベースへの接続を行う。
	 * 
	 * @param srcURL 接続先URL
	 * @return	true:接続成功.false:接続失敗
	 * @throws SQLException
	 */
	public boolean connectTemplate() {

		boolean ret = true;

		for (Iterator iter = m_servers.iterator(); iter.hasNext();) {
			UrlInfo url = (UrlInfo) iter.next();

			String connetStr = PROTOCOL + makeTemplateUrl(url);

			//データソースへのコネクションを確立
			try {
				Connection con = DriverManager.getConnection(connetStr, m_prop);
				m_Connection.add(con);
			} catch (SQLException e) {
				setErrInfo(connetStr, e);
				ret = false;
			}
		}
		return ret;

	}

	/**
	 * コネクション接続
	 * 
	 * 
	 * @param srcURL 接続先URL
	 * @return
	 * @throws SQLException
	 */
	public boolean connect(boolean autocommit) {
		boolean ret = true;
		for (Iterator iter = m_servers.iterator(); iter.hasNext();) {
			UrlInfo url = (UrlInfo) iter.next();
			String connetStr = PROTOCOL + makeUrl(url);

			//データソースへのコネクションを確立
			try {
				Connection con = DriverManager.getConnection(connetStr, m_prop);	
				con.setAutoCommit(autocommit);
				m_Connection.add(con);
			} catch (SQLException e) {
				setErrInfo(connetStr, e);
				ret = false;
			}
		}

		return ret;

	}


	protected Connection getSelectConnectoin(){
		return (Connection)getConnectoin(0);
	
	}

	protected Connection getConnectoin(int index){
		return (Connection)m_Connection.get(index);
	
	}

	/**
	 * コネクション接続
	 * 
	 * 検索用に1つだけ接続を行う。
	 * サーバ情報リストの最初のサーバに接続する。
	 * 
	 * @param srcURL 接続先URL
	 * @return
	 * @throws SQLException
	 */
	public boolean connectSELECT() {


		boolean ret = true;
		UrlInfo url = (UrlInfo) m_servers.get(0);
		String connetStr = PROTOCOL + makeUrl(url);

		//データソースへのコネクションを確立
		try {
			Connection con = DriverManager.getConnection(connetStr, m_prop);
			con.setAutoCommit(false);
			m_Connection.add(con);
		} catch (SQLException e) {
			setErrInfo(connetStr, e);
			ret = false;
		}

		return ret;

	}
	

	/**
	 * クローズ処理
	 *
	 */
	public void close() {

		for (Iterator iter = m_Connection.iterator(); iter.hasNext();) {
			Connection con = (Connection) iter.next();

			try {
				con.close();
			} catch (SQLException e) {
			}

		}

		m_Connection.clear();

	}

	/**
	 *	コミット 
	 * @throws SQLException
	 */
	public void commit()  {

		String connectUrl = "";

		for (Iterator iter = m_Connection.iterator(); iter.hasNext();) {
			Connection con = (Connection) iter.next();

			try {
				connectUrl = ((Jdbc3Connection) con).getURL();
				con.commit();
			} catch (SQLException e) {
				setErrInfo(connectUrl, e);
			}


		}

	}

	/**
	 * ロールバック 
	 *
	 */
	public void rollback() {

		for (Iterator iter = m_Connection.iterator(); iter.hasNext();) {
			Connection con = (Connection) iter.next();

			try {
				con.rollback();
			} catch (SQLException e) {
				//@@
				System.err.println();
			}

		}

	}

	/**
	 * 接続先情報
	 *
	 */
	protected class UrlInfo {
		protected String m_hostName;
		protected String m_portNo;
		protected String m_dbName;

		/**
		 * @return
		 */
		public String getDbName() {
			return m_dbName;
		}

		/**
		 * @return
		 */
		public String getHostName() {
			return m_hostName;
		}

		/**
		 * @param string
		 */
		public void setDbName(String string) {
			m_dbName = string;
		}

		/**
		 * @param string
		 */
		public void setHostName(String string) {
			m_hostName = string;
		}

		/**
		 * @return
		 */
		public String getPortNo() {
			return m_portNo;
		}

		/**
		 * @param string
		 */
		public void setPortNo(String string) {
			m_portNo = string;
		}

	}

	/**
	 * @param strings
	 */
	public String[] getURL() {

		ArrayList urlList = new ArrayList();
		for (Iterator iter = m_servers.iterator(); iter.hasNext();) {
			UrlInfo url = (UrlInfo) iter.next();
			urlList.add(makeUrl(url));
		}
		//メンバ変数に格納

		return (String[]) urlList.toArray(new String[0]);
	}

	/**
	 * @param strings
	 */
	public String getURL(int index) {

		return 	makeUrl((UrlInfo)m_servers.get(index));

	}

	/**
	 *コネクション生成用のプロパティリスト作成 
	 * @param url
	 * @return
	 */
	static public Properties parseURL(String url) {

		int state = -1;
		Properties urlProps = new Properties();

		String key = "";
		String value = "";

		StringTokenizer st = new StringTokenizer(url, ":/;=&?", true);
		for (int count = 0;(st.hasMoreTokens()); count++) {
			String token = st.nextToken();

			if (count == 0 && token.equals("/"))
				state = 0;
			else if (count == 0) {
				urlProps.put("PGDBNAME", token);
				state = -2;
			} else if (count == 1 && state == 0 && token.equals("/"))
				state = 1;
			else if (count == 1 && state == 0)
				return null;
			else if (count == 2 && state == 1)
				urlProps.put("PGHOST", token);
			else if (count == 3 && token.equals(":"))
				state = 2;
			else if (count == 4 && state == 2) {
				try {
					Integer portNumber = Integer.decode(token);
					urlProps.put("PGPORT", portNumber.toString());
				} catch (Exception e) {
					return null;
				}
			} else if (
				(count == 3 || count == 5)
					&& (state == 1 || state == 2)
					&& token.equals("/"))
				state = -1;
			else if (state == -1) {
				urlProps.put("PGDBNAME", token);
				state = -2;
			} else if (state <= -2 && (count % 2) == 1) {
				if (token.equals(";")
					|| token.equals("?")
					|| token.equals("&"))
					state = -3;
				else if (token.equals("="))
					state = -5;
			} else if (state <= -2 && (count % 2) == 0) {
				if (state == -3)
					key = token;
				else if (state == -5) {
					value = token;
					urlProps.put(key, value);
					state = -2;
				}
			}
		}

		return urlProps;

	}

	/**
	 * @return
	 */
	public boolean isErrInfo() {
		if(m_errInfoList.size() == 0){
			
			return false;
		}else{
			return true;
			
		}
	}

	/**
	 * @return
	 */
	public ArrayList getErrInfoList() {
		return m_errInfoList;
	}
	/**
	 * @return
	 */
	public void clearErrInfoList() {
		m_errInfoList.clear();
	}

	protected void setErrInfo(String url, String sql, Exception exception) {
		ErrInfo errInfo = new ErrInfo(url, sql, exception);
		m_errInfoList.add(errInfo);
	}

	protected void setErrInfo(String url, Exception exception) {
		setErrInfo(url, "", exception);
	}

	/**
	 * DB名の一覧取得
	 * 
	 * データベースメタデータよりデータベース名の一覧を取得する。
	 * 各接続にごとに、キー、値ともに、データベース名のHashMapを生成し、
	 * リストに格納して返す
	 * 
	 * @return　DB名の一覧
	 */
	public ArrayList getDbMapList(){

		ArrayList dbMapList = new ArrayList();
		String connectUrl = "";

		for (Iterator iter = m_Connection.iterator(); iter.hasNext();) {

			Connection con = (Connection) iter.next();
			HashMap dbMap = new HashMap();
			try{
	
				connectUrl = ((Jdbc3Connection) con).getURL();
	
				//メタ情報からテーブルの一覧取得
				DatabaseMetaData dataMeta = con.getMetaData();
				
				ResultSet rs = dataMeta.getCatalogs();
				
				while (rs.next()) {
					String dbName = rs.getString("TABLE_CAT");
					//HashMapに登録
					dbMap.put(dbName,dbMap);
				}
				
	
			} catch (SQLException e) {
			}
			//HashMapをリストに登録
			dbMapList.add(dbMap);

		}
		return dbMapList;
	
	}

	/**
	 * 接続先情報リストクリア
	 *
	 */
	public void clearServers() {
		m_servers.clear();
	}


}
