/*
 * $Source: /usr/local/cvsroot/erserver/erserver/java/src/com/postgres/replic/util/Rserv.java,v $
 * $Author: asullivan $ $Revision: 1.1.1.1 $ $Date: 2003/08/26 19:02:29 $
 *
 */

package com.postgres.replic.util;

import com.postgres.replic.server.props.*;
import java.sql.*;


public class Rserv extends AbstractInitializable {

    private Connection masterConn;
    private Connection slaveConn;
    private MasterProcessor masterProcessor = new MasterProcessor();
    private SlaveProcessor slaveProcessor = new SlaveProcessor();
    private boolean snapshotPrepared = false;
    private boolean snapshotApplied = false;
    private int server;
    private ServerProps serverProps;

    public void init(Connection masterConn, Connection slaveConn, int server,
        ServerProps serverProps)
        throws Exception {
            getLogger().debug("Rserv::init BEGIN; serverProps=" + serverProps);
            this.masterConn = masterConn;
            this.slaveConn = slaveConn;
            this.server = server;
            this.serverProps = serverProps;
            validate();
            masterProcessor.reset(this.masterConn, server, serverProps);
            slaveProcessor.reset(this.slaveConn, server, serverProps);

            snapshotPrepared = false;
            snapshotApplied = false;
            this.masterProcessor.getConnection().setAutoCommit(false);
            this.slaveProcessor.getConnection().setAutoCommit(false);

            setInitialized(true);
            getLogger().debug("Rserv::init END");
    }

    public void setDebug(boolean debug) {
        super.setDebug(debug);
        masterProcessor.setDebug(debug);
        slaveProcessor.setDebug(debug);
    }

    private void validate() throws RservException {
        if (masterConn == null) {
            throw new RservException("Master connection may not be null");
        }

        if (slaveConn == null) {
            throw new RservException("Slave connection may not be null");
        }
    }



    private void validateInitialized() throws RservException {
        if (!initialized()) {
            throw new RservException("Rserv::validateInitialized: object is not initialized");
        }
    }

    private boolean prepareSnapshot() throws RservException {
        validateInitialized();
        boolean rc = false;
        if (snapshotPrepared) {
            getLogger().debug("Rserv::prepareSnapshot: snapshot already Prepared");
            return true;
        } else {
            getLogger().debug("Rserv::prepareSnapshot: snapshot is not Prepared - let's prepare it");
        }

        try {

            // Set optimizer hints:
            masterProcessor.setOptimizerHints();

            // Begin transaction:
            masterProcessor.begin();

            // Set transaction isolation to Serializable:
            masterProcessor.setTransactionSerializable();

            // Prepare snapshot:
            masterProcessor.initPrepare();
            boolean rc1 = masterProcessor.prepareTables();

            if (rc1) {
                masterProcessor.rememberSnapshot();
                masterProcessor.commit();
                masterProcessor.closeConnection();
                rc = true;
            }
            snapshotPrepared = true;
        } catch (Exception e) {
            try {
                masterProcessor.rollback();
            } catch (Exception ex) { }
            throw new RservException("Rserv::prepareSnapshot: " + e.toString());
        } finally {
           try {
               masterProcessor.rollback();
           } catch (Exception ex) { }
        }
        return rc;
    }

    private int applySnapshot() throws RservException, RservDuplicateRowException {
        validateInitialized();
        int rc = 0;
        if (!snapshotPrepared) {
            throw new RservException("Rserv::prepareSnapshot: snapshot is not Prepared");
        }

        try {
            slaveProcessor.setOptimizerHints();
            slaveProcessor.begin();
            rc = slaveProcessor.applySnapshot(masterProcessor.getSnapshotData());
            slaveProcessor.commit();
            slaveProcessor.closeConnection();
        } catch (RservDuplicateRowException re) {
            // Duplicate row was deleted: try again:
            rc = applySnapshot();
        } catch (Throwable e) {
            try {
                slaveProcessor.rollback();
            } catch (Exception ex) { }
            throw new RservException("Rserv::applySnapshot: " + e.toString());
        } finally {

        }
        return rc;
    }

    private void syncSync() throws RservException {
        validateInitialized();

        try {
            long syncid = slaveProcessor.GetSyncID();
            if (syncid > 0) {
                getLogger().info("Rserv::syncSync: Last SyncID applied: syncid=" + syncid);
                masterProcessor.SyncSyncID(syncid);
                getLogger().debug("Rserv::syncSync: Succeeded");
            }
            masterProcessor.commit();
        } catch (Exception e) {
            try {
                masterProcessor.rollback();
            } catch (Exception ex) { }
            throw new RservException("Rserv::syncSync: " + e.toString());
        } finally {

        }
    }

    public void replicate() throws RservException, Exception {
        try {
            long time = System.currentTimeMillis();

            getLogger().debug("Rserv::replicate : BEGIN");
            validateInitialized();

            syncSync();
            getLogger().debug("Rserv::replicate : After syncSync()");

            getLogger().info("Rserv::replicate : PREPARE SNAPSHOT BEGINS ...");
            boolean snashotGotSomething = prepareSnapshot();
            long timePrepare = System.currentTimeMillis() - time;
            getLogger().info("Rserv::replicate : PREPARED SNAPSHOT to SERVER "
                + server + " IN " + timePrepare + " MILLIS.; Snapshot Got Something="
                    + snashotGotSomething);

            if (snashotGotSomething) {
                getLogger().info("Rserv::replicate : APPLY SNAPSHOT BEGINS ...");
                int numModified = applySnapshot();
                syncSync();
                time = System.currentTimeMillis() - time;
                double timePerUpdate = (double) time / numModified;
                String form =
                    (new java.text.DecimalFormat("######.##")).format(timePerUpdate);
                getLogger().info("Rserv::replicate : SUCCESSFULLY REPLICATED to SERVER "
                    + server + " IN " + time + " MILLIS.; Average time per 1 update="
                    + form);
            } else {
                getLogger().info("Rserv::replicate : NOTHING TO REPLICATE");
                slaveProcessor.rollback();
                masterProcessor.rollback();
            }
            setInitialized(false); // Force re-initialization after each replication
            getLogger().debug("Rserv::replicate : END");
        } catch (Throwable e) {
            getLogger().warn("Rserv::replicate : catch (Throwable e)");
            // Try to rollback on slave
            try {
                slaveProcessor.rollback();
                masterProcessor.rollback();
            } catch (Exception ex) {
                getLogger().error("Rserv::replicate: Cannot Rollback: " + e.toString());
            }
            // re-throw the exception:
            throw new RservException("Rserv::replicate: " + e.toString());
        } finally {

        }

    }

    /*
    */

    public void cleanLog(int howold) throws RservException {
        validateInitialized();
        try {
            // masterProcessor.cleanLog consists of several transactions:
            long time = System.currentTimeMillis();
            int olt = masterProcessor.cleanLog(howold);
            time = System.currentTimeMillis() - time;
            getLogger().info("Rserv::cleanLog : SUCCESSFULLY CLEANED LOG " +
                olt + " IN " + time + " MILLIS.");
            setInitialized(false); // Force re-initialization after each cleanLog
        } catch (Exception e) {
            try {
                masterProcessor.rollback();
            } catch (Exception ex) {}
            throw new RservException("Rserv::cleanLog: " + e.toString());
        }
    }
}
