package org.postgresforest.tool.lib;

import java.sql.*;
import java.util.ArrayList;
import org.postgresforest.tool.lib.ByteaUtils;
import org.postgresforest.tool.lib.Config;
import org.postgresforest.tool.lib.ForestToolException;
import org.postgresforest.tool.lib.Instance;
import org.postgresforest.tool.ArrayUtil;
import org.postgresforest.tool.Logger;

public class GSCdata {
	int serverId = 0;
	String user = null;
	String pass = null;
	String gscName = null;
	String hostName = null;
	String portNumber = null;
	Connection master = null;
	ArrayList otherGSC = null;

 	public static final int VALIDATE_SCHEMA         = 1;
 	public static final int VALIDATE_RECORD_COUNT   = 2;
 	public static final int VALIDATE_RECORD_COMPARE = 4;

	public static final String TemplateDatabase = "postgres";

	/**
	 * GSCの初期化を行う。
	 *
	 * 空のGSCデータベースを作成、インスタンスとして自分自身を登録するとともに、
	 * 作成したGSCを登録する。
	 * GSCに接続し直し、デフォルトのCONFIGを作成する。
	 *
	 * @param hostName ホスト名
	 * @param portNumber ポート番号
	 * @param gscName GSC名（GSCデータベース名）
	 * @param user 接続ユーザ名
	 * @param pass 接続ユーザパスワード
	 *
	 * @return GSCdata 接続が確立されたGSCオブジェクト
	 */
	public static GSCdata initialize(String hostName, String portNumber,
									 String gscName, String user, String pass)
		throws ForestToolException
	{
		String url = null;
		Connection con = null;
		Statement stmt = null;

		try {
			con = createEmptyGSC(hostName, portNumber, gscName, user, pass);

			/* ----------------------
			 * register instance myself
			 * ----------------------
			 */
			stmt = con.createStatement();
			int rc;

			String u = "//" + hostName + ":" + portNumber + "/";
			String q = "INSERT INTO forest_server (serverid, url, status) "
				     + " VALUES (0, '" + u + "', 1)";
			rc = stmt.executeUpdate( q );

			/* ----------------------
			 * register gsc myself
			 * ----------------------
			 */
			q = "INSERT INTO forest_gsc (serverid, dbname) VALUES (0, '" + gscName + "')";
			rc = stmt.executeUpdate( q );

			con.commit();

			stmt.close();
		}
		catch (SQLException e)
		{
			Logger.debug(e.getMessage());
			Logger.trace(e);

			throw new ForestToolException(e);
		}

		try {
			con.close();
		}
		catch (Exception e)
		{
			Logger.warning("Exception while closing connection is going to be ignored.");
			Logger.debug(e.getMessage());
			Logger.trace(e);
		}

		/* -----------------
		 * connect to gsc
		 * -----------------
		 */
		GSCdata gsc = new GSCdata(hostName, portNumber, gscName, user, pass);

		Config tmpconfig = new Config(gsc, Config.DEFAULT_CONFIG_NAME);
		Config config = gsc.createConfig(Config.DEFAULT_CONFIG_NAME, tmpconfig);

		if ( config==null )
			Logger.error("Can't create FOREST_DEFAULT_CONFIG while initializing.");

		return gsc;
	}

	/**
	 * GSC用の空データベースおよびテーブルを作成し、GSCへの接続を行う
	 *
	 * @param hostName ホスト名
	 * @param portNumber ポート番号
	 * @param gscName GSC名（GSCデータベース名）
	 * @param user GSC接続ユーザ名
	 * @param pass GSC接続ユーザパスワード
	 *
	 * @return Connection GSCデータベースへのConnectionオブジェクト
	 */
	protected static Connection createEmptyGSC(String hostName, String portNumber,
											   String gscName, String user, String pass)
		throws ForestToolException
	{
		String url = null;
		Connection con = null;
		Statement stmt = null;

		try {
			/* ------------------------
			 * create initial gsc
			 * ------------------------
			 */
			String tmpurl = "jdbc:postgresql://" + hostName + ":" + portNumber + "/" + TemplateDatabase;

			con = connectdb(tmpurl, user, pass);
			con.setAutoCommit(true);

			stmt = con.createStatement();			
			int rc;

			rc = stmt.executeUpdate("CREATE DATABASE " + gscName);

			con.close();

			url = "jdbc:postgresql://" + hostName + ":" + portNumber + "/" + gscName;

			con = connectdb(url, user, pass);
			stmt = con.createStatement();			

			rc = stmt.executeUpdate( getGscSchemaDefs() );

			con.commit();
			stmt.close();
		}
		catch (SQLException e)
		{
			Logger.debug(e.getMessage());
			Logger.trace(e);

			throw new ForestToolException(e);
		}

		return con;
	}

	/**
	 * コンストラクタ。引数で指定されたGSCに接続する。
	 * 
	 * @param hostName GSCデータベースに接続するためのホスト名
	 * @param portNumber GSCデータベースに接続するためのポート番号
	 * @param gscName GSC名（GSCデータベース名）
	 * @param user 接続ユーザ名
	 * @param pass 接続ユーザパスワード
	 */
	public GSCdata(String hostName, String portNumber, String gscName,
				   String user, String pass)
		throws ForestToolException
	{
		String url = "//" + hostName + ":" + portNumber + "/";
		String full_url = "jdbc:postgresql:" + url + gscName;

		try {
			open(hostName, portNumber, gscName, user, pass);
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		this.gscName    = gscName;
		this.hostName   = hostName;
		this.portNumber = portNumber;
		this.user       = user;
		this.pass       = pass;
	}

	/**
	 * GSCに現在接続しているユーザ名を取得する
	 *
	 * @return String GSCに接続しているユーザ名
	 */
	public String getUser()
	{
		return user;
	}

    /**
     * GSCに現在接続しているユーザのパスワードを取得する
     *
     * @return String GSCに接続しているユーザのパスワード
     */
	public String getPassword()
	{
		return pass;
	}

	/**
	 * デフォルトのCONFIGをテンプレートとして、
	 * 指定したCONFIG名でCONFIGエントリを作成する
	 *
	 * @param configName 作成するCONFIGエントリのCONFIG名（CONFIG ID）
	 *
	 * @return Config 新規に作成されたCONFIGのConfigオブジェクト
	 */
	public Config createConfig(String configName)
	{
		Config config = getConfig(Config.DEFAULT_CONFIG_NAME);

		return createConfig(configName, config);
	}

	/**
	 * 指定した設定値でCONFIGエントリを作成する。
	 *
	 * @param configName 作成するCONFIGエントリのCONFIG名（CONFIG ID）
	 * @param desc CONFIGエントリの説明文
	 * @param cache_refresh キャッシュのリフレッシュ時間（秒）
	 * @param retry_count 検索実行時に失敗した場合のリトライ回数（0でリトライしない）
	 * @param defect_timeout タイムアウトを検出するまでの時間（秒）
	 * @param distributed_connection 負荷分散時の接続の方法（ラウンドロビン：0、固定接続：1）
	 * @param partition_mode パーティションモード設定（パーティションモード：1、非パーティションモード：0）
	 * @param synchronize_mode 更新同期モード設定（更新同期モード:1、更新非同期モード：2）
	 *
	 * @return Config 新規に作成されたCONFIGのConfigオブジェクト
	 */
	public Config createConfig(String configName, String desc, int cache_refresh,
							   int retry_count, int defect_timeout,
							   int distributed_connection, int partition_mode,
							   int synchronize_mode)
	{
		Config config = new Config(this, configName, desc,
								   cache_refresh, retry_count, defect_timeout,
								   distributed_connection,
								   partition_mode, synchronize_mode);

		return createConfig(config.getId(), config);
	}

    /**
     * 既存のCONFIGをテンプレートとして、
     * 指定したCONFIG名でCONFIGエントリを作成する
     *
     * @param configName 作成するCONFIGエントリのCONFIG名（CONFIG ID）
	 * @param sourceConfigName テンプレートとするCONFIGのCONFIG名
     *
     * @return Config 新規に作成されたCONFIGのConfigオブジェクト
     */
	public Config createConfig(String configName, String sourceConfigName)
	{
		return createConfig(configName,  new Config(this, sourceConfigName));
	}

    /**
     * 既存のCONFIGをテンプレートとして、
     * 指定したCONFIG名でCONFIGエントリを作成する
     *
     * @param configName 作成するCONFIGエントリのCONFIG名（CONFIG ID）
     * @param sourceConfig テンプレートとするCONFIGのConfigオブジェクト
     *
     * @return Config 新規に作成されたCONFIGのConfigオブジェクト
     */
	public Config createConfig(String configName, Config sourceConfig)
	{
		String sql = "INSERT INTO forest_config(configid,description,cache_reflesh,retry_count,defect_timeout,distributed_connection,pertition_mode,syncronize_mode,update_date) VALUES (" +
			"'" + configName + "'," + 
			"'" + sourceConfig.getDescription() + "'," + 
			"" + sourceConfig.getCacheRefresh() + "," + 
			"" + sourceConfig.getRetryCount() + "," + 
			"" + sourceConfig.getDefectTimeout() + "," + 
			"" + sourceConfig.getDistributedConnection() + "," + 
			"" + sourceConfig.getPartitionMode() + "," + 
			"" + sourceConfig.getSynchronizeMode() + "," + 
			"now())";

		int rc = executeUpdateGSC(sql);

		return getConfig(configName);
	}

	/**
	 * CONFIG名の一覧を取得する。
	 *
	 * @return String[] CONFIG名のString配列。存在しない場合は要素数ゼロの配列。
	 */
	public String[] getConfigNames()
		throws ForestToolException
	{
		ArrayList a = new ArrayList();

		try {
			ResultSet rs = executeQueryGSC("SELECT configid FROM forest_config ORDER BY configid");

			while (rs.next())
			{
				a.add(rs.getString(1));
			}

			rs.close();
		}
		catch (Exception e)
		{
            throw new ForestToolException(e);
		}

		return ArrayUtil.array2stringarray(a);
	}

	/**
	 * 指定したCONFIG名のエントリを検索して、Configオブジェクトとして取得する
	 *
	 * @param configName 取得するCONFIG名
	 *
	 * @return Config 該当するCONFIGエントリのConfigオブジェクト、取得に失敗したらnull
	 */
	public Config getConfig(String configName)
	{
        String sql = "SELECT configid,description,cache_reflesh,retry_count,defect_timeout,distributed_connection,pertition_mode,syncronize_mode,update_date FROM forest_config WHERE configid='" + configName + "'";

        ResultSet rs = null;

		try {
			rs = executeQueryGSC(sql);
		}
		catch (Exception e)
		{
			Logger.error("Can't get CONFIG entry. [" + configName + "]");
			Logger.error(e.getMessage());

			return null;
		}

		Config config = null;

        try{
			if ( rs.next() )
			{
				config = new Config(this, configName, rs.getString("description"),
									rs.getInt("cache_reflesh"), rs.getInt("retry_count"),
									rs.getInt("defect_timeout"), rs.getInt("distributed_connection"),
									rs.getInt("pertition_mode"), rs.getInt("syncronize_mode") );
			}
		}
		catch (Exception e)
		{
			Logger.error("Can't build Config object. / " + configName);
			return null;
		}

        return config;
    }

	/**
	 * 指定したIDのCONFIGエントリを削除する。
	 *
	 * @param configId 削除するCONFIGのCONFIG ID
	 *
	 * @return boolean 削除した場合はtrue、削除できなかった場合はfalse
	 */
	public boolean dropConfig(String configId)
	{
		String sql = "DELETE FROM forest_config WHERE configid='" + configId + "'";
		boolean result = false;

		try {
			int rc = executeUpdateGSC(sql);
			if ( rc>0 )
				result = true;
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			result = false;
		}

		return result;
	}

	/**
	 * 指定したConfigオブジェクトのCONFIGエントリを削除する。
	 *
	 * @param config 削除するCONFIGエントリのConfigオブジェクト
	 *
	 * @return boolean 削除した場合はtrue、削除できなかった場合はfalse
	 */
	public boolean dropConfig(Config config)
	{
		if ( config==null )
			return false;

		return dropConfig(config.getId());
	}


	/**
	 * インスタンスをGSCに登録し、該当するInstanceオブジェクトを取得する
	 *
	 * @param hostName 登録するインスタンスのホスト名かIPアドレス
	 * @param portNumber 登録するインスタンスのポート番号
	 *
	 * @return Instance 登録したインスタンスを表すInstanceオブジェクト
	 */
	public Instance registerInstance(String hostName, String portNumber)
	{
		int serverId = -1;

		if ( hostName==null || portNumber==null )
			return null;

        try {
			ResultSet rs = executeQueryGSC("SELECT max(serverid) FROM forest_server");
			rs.next();
			serverId = rs.getInt(1);
			rs.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			return null;
		}

		serverId++;

		String url = "//" + hostName + ":" + portNumber + "/";

		String sql = "INSERT INTO forest_server(serverid,url,status) VALUES (" +
            "" + serverId + "," +
            "'" + url + "'," +
            "" + -1 + ")";

        int rc = executeUpdateGSC(sql);

        return getInstance(serverId);
	}

	/**
	 * GSCに登録されているインスタンスのインスタンスIDを取得する。
	 * インスタンスID順にソートして返却する。
	 *
	 * @return int[] インスタンスIDの配列
	 */
	public int[] getInstanceIds()
		throws ForestToolException
	{
		ArrayList a = new ArrayList();

		try {
			ResultSet rs = executeQueryGSC("SELECT serverid FROM forest_server ORDER BY serverid");

			while ( rs.next() )
			{
				a.add(new Integer( rs.getInt(1) ));
			}

			rs.close();
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		return ArrayUtil.array2intarray(a);
	}

	/**
	 * 指定したサーバIDのインスタンスのInstanceオブジェクトを取得する。
	 * 見つからない場合、例外が返った場合にはnullを返す。
	 *
	 * @param serverid サーバID
	 *
	 * @return Instance 指定したサーバIDのInstanceオブジェクト
	 */
	public Instance getInstance(int serverid)
	{
        int serverId = -1;
		String url = null;
		int status = -1;

        try {
            ResultSet rs = executeQueryGSC("SELECT url,status FROM forest_server WHERE serverid=" + serverid);
            if ( rs.next() )
			{
				url    = rs.getString(1);
				status = rs.getInt(2);
			}
			else
				return null;

            rs.close();
        }
        catch (Exception e)
		{
			Logger.error(e.getMessage());
			return null;
		}

		String tmp = url.substring(2, url.length()-1);

		String host = tmp.substring(0, tmp.indexOf(":"));
		String port = tmp.substring(tmp.indexOf(":")+1);

		Instance instance = new Instance(this, serverid, host, port);

		return instance;
	}

	/**
	 * インスタンスを削除する。
	 *
	 * インスタンス上にデータベースが存在している場合、失敗となる。
	 *
	 * @param instance インスタンスのInstanceオブジェクト
	 *
	 * @return boolean インスタンスを削除したらtrue、削除できない場合はfalse
	 */
	public boolean unregisterInstance(Instance instance)
	{
		if ( instance==null )
			return false;

		return unregisterInstance( instance.getId() );
	}

	/**
	 * インスタンスを削除する。
	 *
	 * インスタンス上にデータベースが存在している場合、失敗となる。
	 *
	 * @param instanceId インスタンスID
	 *
	 * @return boolean インスタンスを削除したらtrue、削除できない場合はfalse
	 */
	public boolean unregisterInstance(int instanceId)
	{
		return unregisterInstance(instanceId, false);
	}

	/**
	 * インスタンスを削除する。
	 *
	 * インスタンス上にデータベースが存在している場合、
	 * forceがtrueの場合にはそれらを削除してインスタンスを削除する。
	 * forceがfalseの場合、データベースが存在していると失敗となる。
	 * GSCは強制削除の対象とはしない。
	 *
	 * @param instanceId インスタンスID
	 * @param force trueの場合、インスタンス削除時に既存のデータベース、GSCを削除する
	 *
	 * @return boolean インスタンスを削除したらtrue、削除できない場合はfalse
	 */
	public boolean unregisterInstance(int instanceId, boolean force)
	{
		Instance ins = getInstance(instanceId);

		if ( ins==null )
			return false;

		String[] dbNames = ins.getDatabaseNames();

		/*
		 * ユーザDBが存在している
		 */
		if ( dbNames.length>0 )
		{
			if ( force )
			{
				/*
				 * ユーザDBを削除
				 */
				Logger.notice("Existing database(s) will be dropped.");
				for (int i=0 ; i<dbNames.length ; i++)
				{
					if ( !dropDatabase(dbNames[i], instanceId, true) )
						return false;
				}
			}
			else
			{
				Logger.error("Instance has some user objects. Please drop them before unregister the instance.");
				return false;
			}
		}
		
		/*
		 * FIXME: GSCは強制的に削除しない？
		 */

		/*
		 * GSCからエントリを削除
		 */
		try {
			String sql = "DELETE FROM forest_server WHERE serverid=" + instanceId;
			int rc = executeUpdateGSC(sql);
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			return false;
		}

		return true;
	}

    /**
     * すべてのインスタンス上にユーザデータベースを作成し、GSCに登録する。
     *
     * @param dbName 作成するデータベース名
     *
     * @return Database 作成されたデータベースを示すDatabaseオブジェクト。失敗時はnull
     */
	public Database createDatabase(String dbName)
		throws ForestToolException
	{
		return createDatabase(getInstanceIds(), dbName);
	}

    /**
     * すべてのインスタンス上にユーザデータベースを作成し、GSCに登録する。
	 * CREATE DATABASE文に引き渡すオプションを指定することができる。
     *
     * @param dbName 作成するデータベース名
	 * @param option データベース作成時に指定するオプション
     *
     * @return Database 作成されたデータベースを示すDatabaseオブジェクト。失敗時はnull
     */
    public Database createDatabase(String dbName, String option)
        throws ForestToolException
    {
        return createDatabase(getInstanceIds(), dbName, option);
    }

    /**
     * 指定したインスタンス上にユーザデータベースを作成し、GSCに登録する。
     *
     * @param instanceIds インスタンスIDを保持した配列
     * @param dbName 作成するデータベース名
     *
     * @return Database 作成されたデータベースを示すDatabaseオブジェクト。失敗時はnull
     */
 	public Database createDatabase(int[] instanceIds, String dbName)
		throws ForestToolException
	{
		return createDatabase(instanceIds, dbName, null);
	}

	/**
	 * 指定したインスタンス上にユーザデータベースを作成し、GSCに登録する。
	 * CREATE DATABASE文に引き渡すオプションを指定することができる。
	 *
	 * @param instanceIds インスタンスIDを保持した配列
	 * @param dbName 作成するデータベース名
	 * @param option データベース作成時に指定するオプション
	 *
	 * @return Database 作成されたデータベースを示すDatabaseオブジェクト。失敗時はnull
	 */
	public Database createDatabase(int[] instanceIds, String dbName, String option)
		throws ForestToolException
	{
		Database d = null;

		try {
			for (int i=0 ; i<instanceIds.length ; i++)
			{
				Instance instance = getInstance(instanceIds[i]);
				if ( instance==null )
					Logger.error("NullPointerException: instanceId=" + instanceIds[i]);

				instance.createDatabase(dbName, option);

				/*
				 * dbName   : 同一データベース内では共通。
				 * dbno     : 同一データベース内で、サーバごとに連番
				 * serverid : インスタンスID（サーバID）
				 *
				 * 主キーは (dbno,dbName)
				 */
				String sql = "INSERT INTO forest_servdb(dbno,dbname,serverid) VALUES (" + i + ",'" + dbName + "'," + instanceIds[i] + ")";
				int rc = executeUpdateGSC(sql);

				d = new Database(this, instanceIds, dbName);
			}
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		return d;
	}

	/**
	 * GSCに登録されているデータベース名の一覧を取得する
	 *
	 * @return String[] データベース名のString配列
	 */
	public String[] getDatabaseNames()
		throws ForestToolException
	{
		String sql = "SELECT dbname FROM forest_servdb GROUP BY dbname ORDER BY dbname";
		ArrayList a = new ArrayList();

		try {
			ResultSet rs = executeQueryGSC(sql);

			while ( rs.next() )
			{
				a.add(rs.getString("dbname"));
			}

			rs.close();
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}

		return ArrayUtil.array2stringarray(a);
	}

	/**
	 * 指定した名前のデータベースを表すDatabaseオブジェクトを取得する
	 *
	 * @param dbName データベース名
	 *
	 * @return Database データベースを表すDatabaseオブジェクト、存在しなかった場合はnullを返す
	 */
	public Database getDatabase(String dbName)
		throws ForestToolException
	{
		String sql = "SELECT serverid FROM forest_servdb WHERE dbname='" + dbName + "' ORDER BY serverid";
		ArrayList a = new ArrayList();

		try {
			ResultSet rs = executeQueryGSC(sql);

			while ( rs.next() )
			{
				int serverid = rs.getInt("serverid");
				a.add( new Integer(serverid) );
			}

			if ( a.size()==0 )
				return null;

			rs.close();
		}
		catch (Exception e)
		{
			throw new ForestToolException(e);
		}


		return new Database(this, ArrayUtil.array2intarray(a), dbName);
	}

	/**
	 * 指定したデータベースを全インスタンス上から削除する。
	 * テーブルが存在していた場合には失敗。
	 *
	 * @param db 削除するデータベースを表すDatabaseオブジェクト
	 *
	 * @return boolean DROPした場合にはtrue、しなかった場合にはfalse
	 */
	public boolean dropDatabase(Database db)
	{
		return dropDatabase(db.getDatabaseName(), false);
	}

	/**
	 * 指定したデータベースを全インスタンス上から削除する。
	 * テーブルが存在していた場合には失敗。
	 *
	 * @param dbName 削除するデータベース名
	 *
	 * @return boolean DROPした場合にはtrue、しなかった場合にはfalse
	 */
	public boolean dropDatabase(String dbName)
	{
		return dropDatabase(dbName, false);
	}

	/**
	 * 指定したデータベースを全インスタンス上から削除する。
	 *
	 * @param db データベースを表すDatabaseオブジェクト
	 * @param instanceId インスタンスID
	 * @param force テーブルが存在していても削除する
	 *
	 * @return boolean DROPした場合にはtrue、しなかった場合にはfalse
	 */
	public boolean dropDatabase(Database db, boolean force)
	{
		return dropDatabase(db.getDatabaseName(), force);
	}

	/**
	 * 指定したデータベースを全インスタンス上から削除する。
	 *
	 * @param dbName データベース名
	 * @param instanceId インスタンスID
	 * @param force テーブルが存在していても削除する
	 *
	 * @return boolean DROPした場合にはtrue、しなかった場合にはfalse
	 */
	public boolean dropDatabase(String dbName, boolean force)
	{
		Database db = null;

		try {
			db = getDatabase(dbName);
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			return false;
		}

		int[] instanceIds = db.getInstanceIds();

		for (int i=0 ; i<instanceIds.length ; i++)
		{
			if ( !dropDatabase(dbName, instanceIds[i], force) )
				return false;
		}

		return true;
	}

	/**
	 * 指定したインスタンス上の指定したデータベースを削除する。
	 * データベース内にテーブルが存在していた場合には削除しない。
	 *
	 * @param dbName データベース名
	 * @param instanceId インスタンスID
	 *
	 * @return boolean DROPした場合にはtrue、しなかった場合にはfalse
	 */
	public boolean dropDatabase(String dbName, int instanceId)
	{
		return dropDatabase(dbName, instanceId, false);
	}

	/**
	 * 指定したインスタンス上の指定したデータベースを削除する。
	 * GSCからも削除する。
	 *
	 * @param dbName データベース名
	 * @param instanceId インスタンスID
	 * @param force テーブルが存在していても削除する
	 *
	 * @return boolean DROPした場合にはtrue、しなかった場合にはfalse
	 */
	public boolean dropDatabase(String dbName, int instanceId, boolean force)
	{
		String sql = null;
		boolean hasObject = false;
		
		/*
		 * データベース内にオブジェクト（テーブル）が存在しているか確認
		 */
		try {
			sql = "SELECT count(t.table_name) FROM forest_servdb s, forest_tablepartdtl t " +
			      " WHERE s.dbname='" + dbName + "' AND s.serverid=" + instanceId + " AND s.dbno=t.dbno";

			ResultSet rs = executeQueryGSC(sql);
			if ( rs.next() && rs.getInt(1) > 0 )
				hasObject = true;
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			return false;
		}

		/*
		 * データベース内にオブジェクト（テーブル）が存在している場合
		 */
		if ( hasObject && !force )
		{
			Logger.warning("Database has some user objects. Please drop them before dropping the database.");
			return false;
		}

		/*
		 * GSCから該当DB・インスタンスの情報を削除
		 */
		try {
			sql = "DELETE FROM forest_tablepartdtl WHERE dbname='" + dbName + "' " +
			      "   AND dbno = ( SELECT dbno FROM forest_servdb " +
			      "                 WHERE serverid=" + instanceId + " AND dbname='" + dbName + "' );";
	
			sql = sql + "DELETE FROM forest_servdb WHERE dbname='" + dbName + "' AND serverid=" + instanceId + ";";
			
			int rc = executeUpdateGSC(sql);

			sql = "SELECT count(*) FROM forest_servdb WHERE dbname='" + dbName + "';";

			ResultSet rs = executeQueryGSC(sql);
			if ( rs.next() && rs.getInt(1)==0 )
			{
				sql = "DELETE FROM forest_partatr WHERE dbname='" + dbName + "';";
				sql = sql + "DELETE FROM forest_tablepart WHERE dbname='" + dbName + "';";
				rc = executeUpdateGSC(sql);
			}
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			return false;
		}

		/*
		 * 実DBをDROP
		 */
		Instance ins = getInstance(instanceId);

		return ins.dropDatabase(dbName);
	}

	/*
	 * 指定したホスト名、ポート番号、データベースに接続して
	 * Connectionオブジェクトを返す。
	 */
	private static Connection connectdb(String host, String port, String db, String user, String pass)
		throws SQLException, ForestToolException
	{
		String url = "jdbc:postgresql://" + host + ":" + port + "/" + db;

		return connectdb(url, user, pass);
	}

	/*
	 * 指定したURLのデータベースに接続、Connectionオブジェクトを返す。
	 */
	private static Connection connectdb(String url, String user, String pass)
		throws SQLException, ForestToolException
	{
		Connection con = null;

		try {
			Class.forName("org.postgresql.Driver");
		}
		catch (ClassNotFoundException e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			throw new ForestToolException(e);
		}

		con = DriverManager.getConnection(url, user, pass);
		Logger.debug("Connected: " + url + ", " + user + ", " + con.toString());

		con.setAutoCommit(false);

		return con;
	}

	/**
	 * GSCへの接続を確立する
	 */
	public void open(String hostName, String portNumber,
					 String gscName, String user, String pass)
		throws SQLException, ForestToolException
	{
		master = connectdb(hostName, portNumber, gscName, user, pass);
		
		otherGSC = new ArrayList();
		
		/* ---------------------
		 * get GSC list
		 * ---------------------
		 */
		Statement stmt = master.createStatement();
		String q = "SELECT s.serverid,s.url,g.dbname "
			+ "  FROM forest_gsc g, forest_server s "
			+ " WHERE g.serverid=s.serverid";
		ResultSet rs = stmt.executeQuery(q);
		
		String url = "//" + hostName + ":" + portNumber + "/";

		while ( rs!=null && rs.next() )
		{
			if ( rs.getString(2).equals(url) )
			{
				serverId = Integer.parseInt(rs.getString(1));
				Logger.debug("open(): my serverid = " + serverId);
			}
			else
			{
				String uu = rs.getString(2);
				String gg = rs.getString(3);
				String full_url = "jdbc:postgresql:" + uu + gg;

				Logger.debug("GSC: " + full_url);

				otherGSC.add(full_url);
			}
		}
		if ( rs!=null )
			rs.close();
	}

	/**
	 * GSCへの接続を閉じる
	 */
	public void close() throws ForestToolException
	{
		try {
			master.close();
		}
		catch (SQLException e)
		{
			Logger.debug(e.getMessage());
			Logger.trace(e);

			throw new ForestToolException(e);
		}
	}

	/*
	 * GSCスキーマ定義を取得する
	 */
	protected static String getGscSchemaDefs()
	{
		String defs = "";

		defs = defs + "CREATE TABLE forest_server ("
			        + "serverid        INT2 NOT NULL, "
			        + "url             VARCHAR(64) NOT NULL, "
                    + "status          INT2 NOT NULL, "
                    + "CONSTRAINT p_key_forest_server PRIMARY KEY(serverid), "
                    + "CONSTRAINT u_key_forest_server UNIQUE(url) "
			        + ");";

		defs = defs + "CREATE TABLE forest_gsc ( "
			        + "serverid        INT2 NOT NULL, "
			        + "dbname          NAME NOT NULL, "
			        + "CONSTRAINT p_key_forest_gsc PRIMARY KEY(serverid,dbname), "
			        + "CONSTRAINT f_key_forest_gsc_server FOREIGN KEY(serverid) REFERENCES forest_server(serverid) DEFERRABLE "
			        + ");";

		defs = defs + "CREATE TABLE forest_servdb ( "
			        + "dbno            INT2 NOT NULL, "
			        + "dbname          NAME NOT NULL, "
			        + "serverid        INT2 NOT NULL, "
			        + "CONSTRAINT p_key_forest_servdb PRIMARY KEY(dbno,dbname), "
			        + "CONSTRAINT u_key_forest_servdb UNIQUE(serverid,dbname), "
			        + "CONSTRAINT f_key_forest_servdb_server FOREIGN KEY(serverid) REFERENCES forest_server(serverid) DEFERRABLE "
			        + ");";

		defs = defs + "CREATE TABLE forest_hash ( "
			        + "name         VARCHAR(256) , "
			        + "description  TEXT , "
			        + "class        bytea, "
			        + "CONSTRAINT p_key_forest_hash PRIMARY KEY(name) "
			        + ");";

		defs = defs + "CREATE TABLE forest_tablepart ( "
			        + "dbname          NAME NOT NULL, "
			        + "table_name      NAME NOT NULL, "
			        + "part_count      INT2 NOT NULL, "
			        + "part_type       INT2 NOT NULL, "
			        + "hash_name       VARCHAR(256), "
			        + "status          INT2 DEFAULT '0' NOT NULL , "
			        + "CONSTRAINT p_key_forest_tablepart PRIMARY KEY(dbname,table_name), "
			        + "CONSTRAINT f_key_forest_tablepart_hash FOREIGN KEY(hash_name) REFERENCES forest_hash(name) DEFERRABLE "
			        + ");";

		defs = defs + "CREATE TABLE forest_tablepartdtl ( "
			        + "dbno            INT2 NOT NULL, "
			        + "dbname          NAME NOT NULL, "
			        + "table_name      NAME NOT NULL, "
			        + "part_no         INT2 NOT NULL, "
			        + "priority        INT2 NOT NULL, "
			        + "CONSTRAINT p_key_forest_tablepartdtl PRIMARY KEY(dbno,dbname,table_name,part_no), "
			        + "CONSTRAINT f_key_forest_tablepartdtl_servdb FOREIGN KEY(dbno,dbname) REFERENCES forest_servdb(dbno,dbname) DEFERRABLE,"
			        + "CONSTRAINT f_key_forest_tablepartdtl_tablepart FOREIGN KEY(dbname,table_name) REFERENCES forest_tablepart(dbname,table_name) DEFERRABLE "
			        + ");";

		defs = defs + "CREATE TABLE forest_partatr ( "
			        + "dbname          NAME NOT NULL, "
			        + "table_name      NAME NOT NULL, "
			        + "column_name     NAME NOT NULL, "
			        + "column_no       INT2 NOT NULL, "
			        + "column_type     NAME NOT NULL, "
			        + "CONSTRAINT p_key_forest_partatr PRIMARY KEY(dbname,table_name,column_name), "
			        + "CONSTRAINT f_key_forest_partatr_tablepart FOREIGN KEY(dbname,table_name) REFERENCES forest_tablepart(dbname,table_name) DEFERRABLE "
			        + ");";
		
		defs = defs + "CREATE TABLE forest_brokenlog ( "
			        + "serverid       INT2 NOT NULL, "
			        + "datetime       TIMESTAMP, "
			        + "client         VARCHAR(64) , "
			        + "msg            TEXT, "
			        + "status         TEXT, "
			        + "query          TEXT, "
			        + "CONSTRAINT f_key_forest_brokenlog_server FOREIGN KEY(serverid) REFERENCES forest_server(serverid) DEFERRABLE "
			        + ");";

		defs = defs + "CREATE TABLE forest_config ( "
			        + "configid        VARCHAR(256) , "
			        + "description     TEXT         , "
			        + "cache_reflesh           INT2 , "
			        + "retry_count             INT2 , "
			        + "defect_timeout          INT2 , "
			        + "distributed_connection  INT2 , "
			        + "pertition_mode          INT2 , "
			        + "syncronize_mode         INT2, "
			        + "update_date     TIMESTAMP  NOT NULL, "
			        + "CONSTRAINT p_key_forest_config PRIMARY KEY(configid) "
			        + ");";

		defs = defs + "REVOKE ALL ON TABLE forest_server,forest_gsc,forest_servdb,forest_hash,forest_tablepart,forest_tablepartdtl,forest_partatr,forest_brokenlog,forest_config FROM public;";
		defs = defs + "GRANT SELECT ON TABLE forest_server,forest_gsc,forest_servdb,forest_hash,forest_tablepart,forest_tablepartdtl,forest_partatr,forest_brokenlog,forest_config TO public;";
		defs = defs + "GRANT UPDATE ON TABLE forest_server,forest_config TO public;";
		defs = defs + "GRANT INSERT ON TABLE forest_brokenlog TO public;";

		return defs;
	}

	/**
	 * GSCに対して検索クエリを実行する。
	 *
	 * 接続しているマスタGSCに対して検索処理を実行する。
	 *
	 * @param sql 実行する検索クエリ
	 *
	 * @return ResultSet 検索結果を格納したResultSet
	 */
    protected ResultSet executeQueryGSC(String sql)
		throws ForestToolException
    {
		ResultSet rs = null;

		Logger.debug("GSC: " + sql);

		try {
			Statement stmt = master.createStatement();

			rs = stmt.executeQuery(sql);
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			rs = null;

			throw new ForestToolException(e);
		}

        return rs;
    }

	/*
	 * マスタGSC以外のGSCへ接続し、ConnectionオブジェクトをArrayListに格納して返す
	 */
    protected ArrayList connectOthers()
	{
		ArrayList a = new ArrayList();

		for (int i=0 ; i<otherGSC.size() ; i++)
		{
			String url = (String)otherGSC.get(i);

			try {
				a.add( connectdb(url, getUser(), getPassword()) );
			}
			catch (Exception e)
			{
				Logger.warning("Exception caught while opening GSC connection, but ignored.");
				Logger.error(e.getMessage());
				Logger.trace(e);
			}
		}

		return a;
	}

	/*
	 * ArrayListで受け取ったConnectionオブジェクトを
	 * すべてcloseする。
	 */
    protected void closeOthers(ArrayList a)
	{
		for (int i=0 ; i<a.size() ; i++)
		{
			Connection con = (Connection)a.get(i);

			try {
				con.close();
			}
			catch (SQLException e)
			{
				Logger.warning("Exception caught while closing GSC connection, but ignored.");
				Logger.error(e.getMessage());
				Logger.trace(e);
			}
		}
	}

	/**
     * GSCに対して更新クエリを実行する。
     *
     * マスタGSCおよびその他のGSCに対して更新処理を実行する。
	 *
	 * マスタGSCの更新に失敗した場合には、その他のGSCの更新は行わない。
	 *
     * マスタ以外のGSCの更新の過程で例外が発生したら、
	 * ワーニングを出力して処理を継続する。
     *
     * @param sql 実行する更新クエリ
     *
     * @return int 更新したGSCの台数
     */
	protected int executeUpdateGSC(String sql)
	{
		int updates = 0;
		int rc = 0;

		try {
			/*
			 * マスタGSCを更新
			 */
			master.setAutoCommit(false);
			Statement stmt = master.createStatement();
			
			Logger.debug("GSC: " + sql);

			stmt.executeUpdate("SET CONSTRAINTS ALL DEFERRED;");
			rc = stmt.executeUpdate(sql);

			stmt.close();
			master.commit();
			if ( rc>0 )
				updates++;
		}
		catch (Exception e)
		{
			Logger.error("Could not update the master GSC.");
			Logger.error(e.getMessage());
			Logger.trace(e);

			rollbackWithoutError(master);

			/*
			 * マスタの更新に失敗した場合は、
			 * その他のGSCの更新は行わない。
			 */
			return updates;
		}

		/*
		 * その他のGSCを更新
		 */
		ArrayList a = connectOthers();

		for (int i=0 ; i<a.size() ; i++)
		{
			Connection con = null;

			try {
				con = (Connection)a.get(i);
				con.setAutoCommit(false);
				Statement stmt = con.createStatement();

				Logger.debug("GSC: " + sql);

				stmt.executeUpdate("SET CONSTRAINTS ALL DEFERRED;");
				int rc2 = stmt.executeUpdate(sql);

				stmt.close();

				/*
				 * マスタとの更新結果に相違
				 */
				if ( rc!=rc2 )
				{
					Logger.warning("Dangerous update detected while updating GSC(s).");
					Logger.warning("  rc=" + rc + ",rc2=" + rc2);
				}
				
				con.commit();
				if ( rc2>0 )
					updates++;
			}
			catch (Exception e)
			{
				Logger.warning("Exception going to be ignored while updating GSC(s).");
				Logger.warning(e.getMessage());
				Logger.trace(e);

				rollbackWithoutError(con);
			}
		}

		closeOthers(a);

		return updates;
	}

	/**
	 * 指定した名前のパーティション関数の情報を取得する（forest_hashから取得）
	 *
	 * @return HashInfo パーティション関数の情報を保持したHashInfoオブジェクト
	 */
	public HashInfo getHash(String hashName)
	{
		HashInfo hash = null;

		try {
			ResultSet rs = executeQueryGSC("SELECT description,class FROM forest_hash " +
										   " WHERE name='" + hashName + "'");

			if ( rs.next() )
			{
				hash = new HashInfo(hashName, rs.getString(1), rs.getBytes(2));
			}

			rs.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
		}

		return hash;
	}

	/**
	 * 登録されているパーティション関数の一覧を取得する
	 *
	 * @return String[] パーティション関数名を保持したStringの配列、失敗した場合はnullを返す
	 */
	public String[] getHashNames()
	{
		ArrayList a = new ArrayList();

		try {
			ResultSet rs = executeQueryGSC("SELECT name FROM forest_hash ORDER BY name");

			while (rs.next())
			{
				a.add( rs.getString(1) );
			}

			rs.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
		}

		return ArrayUtil.array2stringarray(a);
	}

	/**
	 * パーティション関数を登録する
	 *
	 * @param hashName 登録するパーティション関数名
	 * @param desc パーティション関数に関する説明
	 * @param classImage パーティション関数クラスのバイナリイメージ
	 *
	 * @return boolean 登録に成功したらtrue、失敗したらfalse
	 */
	public boolean registerHash(String hashName, String desc, byte[] classImage)
	{
		boolean result = false;

		try {
			String classImageS = ByteaUtils.encodeBytes(classImage);

			String sql = "INSERT INTO forest_hash(name,description,class) VALUES (" +
			             "'" + hashName + "'," +
			             "'" + desc + "'," +
			             "'" + classImageS + "')";

			int rc = executeUpdateGSC(sql);

			if ( rc>0 )
				result = true;
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
		}

		return result;
	}

	/**
	 * 登録されているパーティション関数を削除する。
	 *
	 * @return boolean 削除したらtrue、失敗あるいは存在しなければfalse
	 */
	public boolean unregisterHash(String hashName)
	{
		boolean result = false;

		try {
			int rc = executeUpdateGSC("DELETE FROM forest_hash WHERE name='" + hashName + "'");

			if ( rc>0 )
				result = true;
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
		}

		return result;
	}

	/**
	 * FIXME:
	 */
	public String getGscName()
	{
		return gscName;
	}

	/**
	 * 指定したインスタンスにGSCをコピーする。
	 *
	 * まず、実際のインスタンス上に空GSCを作成、
	 * データをコピーしてから、GSC（forest_gsc）に登録する。
	 *
	 * @param target GSCをコピーするインスタンスのInstanceオブジェクト
	 *
	 * @return boolean コピーしたらtrue、できなければfalse
	 */
	public boolean copy(Instance target)
	{
		return copy(target, false);
	}

	/**
	 * 指定したインスタンスにGSCをコピーする。
	 *
	 * まず、実際のインスタンス上に空GSCを作成、
	 * データをコピーしてから、GSC（forest_gsc）に登録する。
	 *
	 * @param target GSCをコピーするインスタンスのInstanceオブジェクト
	 * @param force 既存のGSCがあったら削除する
	 *
	 * @return boolean コピーしたらtrue、できなければfalse
	 */
	public boolean copy(Instance target, boolean force)
	{
		if ( target==null )
			return false;

		if ( force )
			drop(target);

		return _copy(target);
	}

	/**
	 * 指定したインスタンスにGSCをコピーする。
	 *
	 * まず、実際のインスタンス上に空GSCを作成、
	 * データをコピーしてから、GSC（forest_gsc）に登録する。
	 *
	 * @param target GSCをコピーするインスタンスのInstanceオブジェクト
	 *
	 * @return boolean コピーしたらtrue、できなければfalse
	 */
	private boolean _copy(Instance target)
	{
		Connection con = null;

		try {
			con = createEmptyGSC(target.getHostName(),
								 target.getPortNumber(),
								 getGscName(),
								 getUser(),
								 getPassword());
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			closeWithoutError(con);

			return false;
		}

		String tableName = null;
		String[] columnNames = null;
		int[] colTypes = null;

		tableName = "forest_server";
		columnNames = new String[] { "serverid", "url", "status" };
		colTypes = new int[] { 0, 1, 0 };
		if ( !copyTable(master, con, tableName, columnNames, colTypes) )
			return false;

		tableName = "forest_servdb";
		columnNames = new String[] { "dbno", "dbname", "serverid" };
		colTypes = new int[] { 0, 1, 0 };
		if ( !copyTable(master, con, tableName, columnNames, colTypes) )
			return false;

		tableName = "forest_tablepart";
		columnNames = new String[] { "dbname", "table_name", "part_count", "part_type", "hash_name", "status" };
		colTypes = new int[] { 1, 1, 0, 0, 1, 0 };
		if ( !copyTable(master, con, tableName, columnNames, colTypes) )
			return false;

		tableName = "forest_tablepartdtl";
		columnNames = new String[] { "dbno", "dbname", "table_name", "part_no", "priority" };
		colTypes = new int[] { 0, 1, 1, 0, 0 };
		if ( !copyTable(master, con, tableName, columnNames, colTypes) )
			return false;

		tableName = "forest_partatr";
		columnNames = new String[] { "dbname", "table_name", "column_name", "column_no", "column_type" };
		colTypes = new int[] { 1, 1, 1, 0, 1 };
		if ( !copyTable(master, con, tableName, columnNames, colTypes) )
			return false;

		tableName = "forest_hash";
		columnNames = new String[] { "name", "description", "class" };
		colTypes = new int[] { 1, 1, 1 };
		if ( !copyTable(master, con, tableName, columnNames, colTypes) )
			return false;

		tableName = "forest_gsc";
		columnNames = new String[] { "serverid", "dbname" };
		colTypes = new int[] { 0, 1 };
		if ( !copyTable(master, con, tableName, columnNames, colTypes) )
			return false;

		tableName = "forest_config";
		columnNames = new String[] { "configid", "description", "cache_reflesh", "retry_count", "defect_timeout",
									 "distributed_connection", "pertition_mode", "syncronize_mode",
									 "update_date" };
		colTypes = new int[] { 1, 1, 0, 0, 0, 0, 0, 0, 2 };
		if ( !copyTable(master, con, tableName, columnNames, colTypes) )
			return false;

		try {
			int rc = executeUpdateGSC("INSERT INTO forest_gsc(serverid,dbname) VALUES (" +
									  target.getId() + "," +
									  "'" + getGscName() + "')");
			
			rc = con.createStatement().executeUpdate("INSERT INTO forest_gsc(serverid,dbname) VALUES (" +
													 target.getId() + ",'" + getGscName() + "')");
			con.commit();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			closeWithoutError(con);

			return false;
		}

		try {
			con.close();

			close();
			open(hostName, portNumber, gscName, user, pass);
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			return false;
		}

		return true;
	}

	/*
	 * テーブル内のレコードをコピーする
	 *
	 * colType: 0 / 数値型
	 *          1 / 文字列型
	 *          2 / タイムスタンプ型
	 */
	private boolean copyTable(Connection srccon, Connection dstcon,
							  String tableName, String[] columnNames, int[] colTypes)
	{
		try {
			String selectSQL = buildSelectSQL(tableName, columnNames);
			String insertSQL = buildInsertSQL(tableName, columnNames);

			ResultSet rs = srccon.createStatement().executeQuery(selectSQL);
			PreparedStatement pstmt = dstcon.prepareStatement(insertSQL);

			while ( rs.next() )
			{
				for (int i=0 ; i<columnNames.length ; i++)
				{
					if ( colTypes[i]==0 )
						pstmt.setInt(i+1, rs.getInt(i+1));
					else if ( colTypes[i]==1 )
						pstmt.setString(i+1, rs.getString(i+1));
					else if ( colTypes[i]==2 )
						pstmt.setTimestamp(i+1, rs.getTimestamp(i+1));
				}
				pstmt.executeUpdate();
			}
			pstmt.close();
			rs.close();

			dstcon.commit();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			return false;
		}

		return true;
	}

	/*
	 * テーブルから全カラム・全レコードをSELECTするSQL文を作成する
	 */
	private String buildSelectSQL(String tableName, String[] columnNames)
	{
		/*
		 * SELECT
		 */
		String selectSql = "SELECT ";

		for (int i=0 ; i<columnNames.length ; i++)
		{
			if ( i>0 )
				selectSql = selectSql + ",";
			
			selectSql = selectSql + columnNames[i];
		}

		selectSql = selectSql + " FROM " + tableName;

		return selectSql;
	}

	/*
	 * テーブルの全カラムにINSERTするSQL文を生成する
	 * （プレースホルダ付き、PreparedStatement用）
	 */
	private String buildInsertSQL(String tableName, String[] columnNames)
	{
		/*
		 * INSERT
		 */
		String insertSql = "INSERT INTO " + tableName + "(";

		for (int i=0 ; i<columnNames.length ; i++)
		{
			if ( i>0 )
				insertSql = insertSql + ",";
			
			insertSql = insertSql + columnNames[i];
		}

		insertSql = insertSql + " ) VALUES ( ";

		for (int i=0 ; i<columnNames.length ; i++)
		{
			if ( i>0 )
				insertSql = insertSql + ",";
			
			insertSql = insertSql + '?';
		}

		insertSql = insertSql + " )";

		return insertSql;
	}

	/**
	 * 指定したインスタンスからGSCを削除する。
	 *
	 * まず、GSC（forest_gsc）からエントリを削除してから、
	 * 実際のインスタンス上のGSCデータベースを削除する。
	 *
	 * @param target GSCを削除するインスタンスのInstanceオブジェクト
	 *
	 * @return boolean 削除したらtrue、しなければfalse
	 */
	public boolean drop(Instance target)
	{
		Connection con = null;

		try {
			con = target.getDatabaseConnection(TemplateDatabase, getUser(), getPassword());
			
			/*
			 * GSCからエントリを削除
			 */
			int rc = executeUpdateGSC("DELETE FROM forest_gsc WHERE serverid=" + target.getId());
			int[] instanceIds = getInstanceIds();

			String h = null;
			String p = null;
			for (int i=0 ; i<instanceIds.length ; i++)
			{
				if ( instanceIds[i]!=target.getId() )
				{
					h = getInstance(instanceIds[i]).getHostName();
					p = getInstance(instanceIds[i]).getPortNumber();
					break;
				}
			}

			close();
			open(h, p, gscName, getUser(), getPassword());
			
			/*
			 * 実DBを削除
			 */
			Thread.sleep(3000);  // バックエンドのタイミングによっては失敗するためsleep
			con.createStatement().executeUpdate("DROP DATABASE " + getGscName());
			con.commit();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			return false;
		}
		finally
		{
			closeWithoutError(con);
		}

		return true;
	}

	/**
	 * 指定したインスタンス上にあるGSCと内容が同等かどうかを確認する。
	 *
	 * @param ins 比較先のインスタンスのInstanceオブジェクト
	 *
	 * @return boolean 同一内容だったらtrue、違いがあったらfalse
	 */
	public boolean equals(Instance ins)
	{
		Connection con1 = null;
		Connection con2 = null;
		Statement stmt1 = null;
		Statement stmt2 = null;
		ResultSet rs1 = null;
		ResultSet rs2 = null;

		boolean rc = true;

		try {
			con1 = GSCdata.connectdb(hostName, portNumber,
									 gscName, getUser(), getPassword());
			con2 = GSCdata.connectdb(ins.getHostName(), ins.getPortNumber(),
									 gscName, getUser(), getPassword());

			stmt1 = con1.createStatement();
			stmt2 = con2.createStatement();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			closeWithoutError(con1);
			closeWithoutError(con2);

			return false;
		}

		String sql = null;
		int colnum = 0;

		/*
		 * forest_config
		 */
		sql = "SELECT configid,description,cache_reflesh,retry_count,defect_timeout,distributed_connection,pertition_mode,syncronize_mode,update_date FROM forest_config ORDER BY configid";
		colnum = 9;
		
		rc = compareTable(stmt1, stmt2, sql, colnum);
		if ( !rc )
			Logger.notice(ins + ", forest_config comparison failed.");
		else
			Logger.debug(ins + ", forest_config ok.");

		/*
		 * forest_gsc
		 */
		sql = "SELECT serverid,dbname FROM forest_gsc ORDER BY serverid,dbname";
		colnum = 2;
		
		rc = compareTable(stmt1, stmt2, sql, colnum);
		if ( !rc )
			Logger.notice(ins + ", forest_gsc comparison failed.");
		else
			Logger.debug(ins + ", forest_gsc ok.");

		/*
		 * forest_hash
		 */
		sql = "SELECT name,description,class FROM forest_hash ORDER BY name";
		colnum = 3;
		
		rc = compareTable(stmt1, stmt2, sql, colnum);
		if ( !rc )
			Logger.notice(ins + ", forest_hash comparison failed.");
		else
			Logger.debug(ins + ", forest_hash ok.");

		/*
		 * forest_server
		 */
		sql = "SELECT serverid,url,status FROM forest_server ORDER BY serverid";
		colnum = 3;
		
		rc = compareTable(stmt1, stmt2, sql, colnum);
		if ( !rc )
			Logger.notice(ins + ", forest_server comparison failed.");
		else
			Logger.debug(ins + ", forest_server ok.");

		/*
		 * forest_servdb
		 */
		sql = "SELECT dbno,dbname,serverid FROM forest_servdb ORDER BY dbno,dbname";
		colnum = 3;
		
		rc = compareTable(stmt1, stmt2, sql, colnum);
		if ( !rc )
			Logger.notice(ins + ", forest_servdb comparison failed.");
		else
			Logger.debug(ins + ", forest_servdb ok.");

		/*
		 * forest_tablepart
		 */
		sql = "SELECT dbname,table_name,part_count,part_type,hash_name,status FROM forest_tablepart ORDER BY dbname,table_name";
		colnum = 6;
		
		rc = compareTable(stmt1, stmt2, sql, colnum);
		if ( !rc )
			Logger.notice(ins + ", forest_tablepart comparison failed.");
		else
			Logger.debug(ins + ", forest_tablepart ok.");

		/*
		 * forest_tablepartdtl
		 */
		sql = "SELECT dbno,dbname,table_name,part_no,priority FROM forest_tablepartdtl ORDER BY dbno,dbname,table_name,part_no";
		colnum = 5;
		
		rc = compareTable(stmt1, stmt2, sql, colnum);
		if ( !rc )
			Logger.notice(ins + ", forest_tablepartdtl comparison failed.");
		else
			Logger.debug(ins + ", forest_tablepartdtl ok.");

		/*
		 * forest_partatr
		 */
		sql = "SELECT dbname,table_name,column_name,column_no,column_type FROM forest_partatr ORDER BY dbname,table_name,column_name";
		colnum = 5;
		
		rc = compareTable(stmt1, stmt2, sql, colnum);
		if ( !rc )
			Logger.notice(ins + ", forest_partatr comparison failed.");
		else
			Logger.debug(ins + ", forest_partatr ok.");

		try {
			con1.close();
			con2.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			return false;
		}

		return rc;
	}

	/*
	 * テーブルのデータの内容を比較する。
	 */
	private boolean compareTable(Statement stmt1, Statement stmt2, String sql, int colnum)
	{
		ResultSet rs1 = null;
		ResultSet rs2 = null;
		boolean rc = true;

		try {
			rs1 = stmt1.executeQuery(sql);
			rs2 = stmt2.executeQuery(sql);

			while ( rs1.next() )
			{
				if ( !rs2.next() )
				{
					rc = false;
					break;
				}

				for (int i=1 ; i<=colnum ; i++)
				{
					String c1 = rs1.getString(i);
					String c2 = rs2.getString(i);
					
					Logger.debug(" [" + c1 + ":" + c2 + "]");

					if ( c1==null && c2==null )
					{
						/* ok */
					}
					else if ( c1==null || c2==null )
					{
						rc = false;
						break;
					}
					else if ( !c1.equals(c2) )
					{
						rc = false;
						break;
					}
				}
			}
			if ( rs2.next() )
				rc = false;

		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			rc = false;
		}

		return rc;
	}

	/*
	 * コネクションを閉じる。
	 * 例外が発生しても、メッセージだけを出して無視する。
	 */
	private void closeWithoutError(Connection con)
	{
		try {
			con.close();
		}
		catch (Exception e)
		{
			Logger.warning("Exception caught while closing connection, but going to be ignored.");
			Logger.error(e.getMessage());
			Logger.trace(e);
		}
	}

	private void rollbackWithoutError(Connection con)
	{
		try {
			con.rollback();
		}
		catch (Exception e)
		{
			Logger.warning("Exception caught while roll-back, but going to be ignored.");
			Logger.error(e.getMessage());
			Logger.trace(e);
		}
	}

	/**
	 * brokenlogの内容を取得する
	 *
	 * @return BrokenLog[] BrokenLogオブジェクトの配列。失敗した場合にはnullを返却する。
	 */
	public BrokenLog[] getBrokenLogs()
	{
		ResultSet rs = null;
		ArrayList a = new ArrayList();

		String sql = "SELECT serverid,datetime,client,msg,status,query " +
		             "  FROM forest_brokenlog ORDER BY datetime";

		try {
			rs = executeQueryGSC(sql);

			while ( rs.next() )
			{
				BrokenLog b = new BrokenLog(rs.getInt("serverid"),
											rs.getTimestamp("datetime"),
											rs.getString("client"),
											rs.getString("msg"),
											rs.getString("status"),
											rs.getString("query"));

				a.add( b );
			}

			rs.close();
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);

			return null;
		}

		BrokenLog[] bb = new BrokenLog[a.size()];

		for (int i=0 ; i<a.size() ; i++)
		{
			bb[i] = (BrokenLog)a.get(i);
		}

		return bb;
	}

	/**
	 * 全GSCのbrokenlogの内容を消去する。
	 */
	public void clearBrokenLogs()
	{
		String sql = "DELETE FROM forest_brokenlog";

		try {
			int rc = executeUpdateGSC(sql);
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
		}
	}

	/**
	 * 現在のGSCを保持しているインスタンスのサーバIDを取得する（forest_gscから取得）。
	 * GSCが多重化されている場合に複数のサーバIDが返却される。
	 *
	 * @return int[] サーバIDのint配列、失敗した場合にはnull
	 */
	public int[] getGscInstances()
	{
		ArrayList a = new ArrayList();

		try {
			ResultSet rs = executeQueryGSC("SELECT serverid FROM forest_gsc WHERE dbname='" + gscName + "'");

			while (rs.next())
			{
				a.add( new Integer(rs.getInt(1)) );
			}
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			return null;
		}

		return ArrayUtil.array2intarray(a);
	}

    /**
     * データベースの整合性を確認する。
	 * 確認内容・方法はフラグで指定する。
	 *
	 * VALIDATE_SCHEMA         : GSCとテーブルスキーマとの整合性確認。冗長構成のGSCの不整合チェック。
	 * VALIDATE_RECORD_COUNT   : 全テーブルのレコード数比較
	 * VALIDATE_RECORD_COMPARE : 全テーブルの全レコードの内容比較
	 *
	 * @param flags 動作モードを指定する
     */
	public boolean validate(int flags)
	{
		boolean rc = true;

		try {
			String[] dbNames = getDatabaseNames();
			for (int i=0 ; i<dbNames.length ; i++)
			{
				Database d = getDatabase(dbNames[i]);

				if ( !d.validate(flags) )
				{
					Logger.error("Database `" + d.getDatabaseName() + "' is under inconsistent.");
					rc = false;
				}
				else
				{
					Logger.debug("Database `" + d.getDatabaseName() + "' is under consistent.");
				}
			}
		}
		catch (Exception e)
		{
			Logger.error(e.getMessage());
			Logger.trace(e);
			return false;
		}
  
		return rc;
  	}

	
}
