package org.jacorb.transaction;

/*
 *        JacORB transaction service - a free TS for JacORB
 *
 *   Copyright (C) 1999-2004 LogicLand group Alex Sinishin.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

import java.util.Vector;
import org.omg.CosTransactions.Control;
import org.omg.CosTransactions.ControlHelper;
import org.omg.CosTransactions.ControlOperations;
import org.omg.CosTransactions.ControlPOATie;
import org.omg.CosTransactions.Coordinator;
import org.omg.CosTransactions.CoordinatorHelper;
import org.omg.CosTransactions.CoordinatorOperations;
import org.omg.CosTransactions.CoordinatorPOATie;
import org.omg.CosTransactions.HeuristicCommit;
import org.omg.CosTransactions.HeuristicHazard;
import org.omg.CosTransactions.HeuristicMixed;
import org.omg.CosTransactions.HeuristicRollback;
import org.omg.CosTransactions.Inactive;
import org.omg.CosTransactions.NotPrepared;
import org.omg.CosTransactions.NotSubtransaction;
import org.omg.CosTransactions.PropagationContext;
import org.omg.CosTransactions.RecoveryCoordinator;
import org.omg.CosTransactions.Resource;
import org.omg.CosTransactions.Status;
import org.omg.CosTransactions.SubtransactionAwareResource;
import org.omg.CosTransactions.SubtransactionsUnavailable;
import org.omg.CosTransactions.Synchronization;
import org.omg.CosTransactions.SynchronizationUnavailable;
import org.omg.CosTransactions.Terminator;
import org.omg.CosTransactions.TerminatorHelper;
import org.omg.CosTransactions.TerminatorOperations;
import org.omg.CosTransactions.TerminatorPOATie;
import org.omg.CosTransactions.Unavailable;
import org.omg.CosTransactions.Vote;


/**
 * Instances of this class represent transactions. A single instance
 * is used for implementing the Coordinator, Terminator and Control 
 * CORBA objects.
 *
 * @author Nicolas Noffke
 * @author Vladimir Mencl
 * @version $Id: CoordinatorImpl.java,v 1.10 2008-11-14 08:55:36 nick.cross Exp $
 */

public class CoordinatorImpl 
    implements Sleeper, CoordinatorOperations, 
               ControlOperations, TerminatorOperations 
{
    private Terminator        term_ref;
    private TerminatorPOATie  term_skel;
    private Coordinator       coord_ref;
    private CoordinatorPOATie coord_skel;
    private Control           contr_ref;
    private ControlPOATie     contr_skel;

    private int               transaction_id;
    private int               hash_code;

    private int               status;
    private String            stat_semaphore;

    private Vector            resources;
    private Vector            votes;
    private Vector            syncs;

    private org.omg.PortableServer.POA poa;

    CoordinatorImpl(org.omg.PortableServer.POA _poa, int _trans_id,
                    int _hash_code, int time)
    {
        transaction_id = _trans_id;
        hash_code      = _hash_code;
        poa = _poa;
        stat_semaphore = new String("sss");
        resources      = new Vector();
        syncs          = new Vector();
        votes          = new Vector();
        try {
            coord_skel = new CoordinatorPOATie(this);
            coord_ref  = CoordinatorHelper.narrow(poa.servant_to_reference(coord_skel));
            term_skel  = new TerminatorPOATie(this);
            term_ref   = TerminatorHelper.narrow(poa.servant_to_reference(term_skel));

            contr_skel = new ControlPOATie(this);
            contr_ref  = ControlHelper.narrow(poa.servant_to_reference(contr_skel));

        } catch (org.omg.PortableServer.POAPackage.ServantNotActive esn){
            throw new org.omg.CORBA.INTERNAL();
        } catch (org.omg.PortableServer.POAPackage.WrongPolicy ew){
            throw new org.omg.CORBA.INTERNAL();
        }
        status = Status._StatusActive;
        if (time != 0){
            TransactionService.get_timer().add_channel(this, time);
        }
    }

    private void destroy(){
        try {
            byte[] oid = poa.reference_to_id(term_ref);
            poa.deactivate_object(oid);
            term_ref._release();

            oid = poa.reference_to_id(coord_ref);
            poa.deactivate_object(oid);
            coord_ref._release();

            oid = poa.reference_to_id(contr_ref);
            poa.deactivate_object(oid);
            contr_ref._release();

        } catch (org.omg.PortableServer.POAPackage.ObjectNotActive esn){
            throw new org.omg.CORBA.INTERNAL();
        } catch (org.omg.PortableServer.POAPackage.WrongPolicy ew){
            throw new org.omg.CORBA.INTERNAL();
        } catch (org.omg.PortableServer.POAPackage.WrongAdapter ew){
            throw new org.omg.CORBA.INTERNAL();
        }
        TransactionService.release_coordinator(hash_code);
    }

    private boolean move_to_state(int new_status){
        synchronized(stat_semaphore){
            switch(status){
            case Status._StatusActive:
                switch(new_status){
                case Status._StatusMarkedRollback:
                case Status._StatusPreparing:
                case Status._StatusRollingBack:
                    status = new_status;
                    return true;
                default:
                    return false;
                }
            case Status._StatusMarkedRollback:
                switch(new_status){
                case Status._StatusRollingBack:
                    status = new_status;
                    return true;
                default:
                    return false;
                }
            case Status._StatusPrepared:
                switch(new_status){
                case Status._StatusCommitting:
                    status = new_status;
                    return true;
                default:
                    return false;
                }
            case Status._StatusCommitted:
                switch(new_status){
                case Status._StatusNoTransaction:
                    status = new_status;
                    return true;
                default:
                    return false;
                }
            case Status._StatusRolledBack:
                switch(new_status){
                case Status._StatusNoTransaction:
                    status = new_status;
                    return true;
                default:
                    return false;
                }
            case Status._StatusUnknown:
                throw new org.omg.CORBA.INTERNAL();
            case Status._StatusNoTransaction:
                throw new org.omg.CORBA.INTERNAL();
            case Status._StatusPreparing:
                switch(new_status){
                case Status._StatusRollingBack:
                case Status._StatusPrepared:
                    status = new_status;
                    return true;
                default:
                    return false;
                }
            case Status._StatusCommitting:
                switch(new_status){
                case Status._StatusCommitted:
                    status = new_status;
                    return true;
                default:
                    return false;
                }
            case Status._StatusRollingBack:
                switch(new_status){
                case Status._StatusRolledBack:
                    status = new_status;
                    return true;
                default:
                    return false;
                }
            default:
                throw new org.omg.CORBA.INTERNAL();
            }
        }
    }

    public void wakeup(){
        TransactionService.get_timer().kill_channel(this);
        if (move_to_state(Status._StatusRollingBack)){
            rolling_to_back();
        }
    }

    int _get_transaction_id(){
        return transaction_id;
    }

    Control _get_control(){
        return contr_ref;
    }

    public Status get_status(){
        return Status.from_int(status);
    }

    public Status get_parent_status(){
        throw new org.omg.CORBA.NO_IMPLEMENT();
    }

    public Status get_top_level_status(){
        return Status.from_int(status);
    }

    public boolean is_same_transaction(Coordinator tc){
        return (hash_code == tc.hash_transaction());
    }

    public boolean is_related_transaction(Coordinator tc){
        throw new org.omg.CORBA.NO_IMPLEMENT();
    }

    public boolean is_ancestor_transaction(Coordinator tc){
        throw new org.omg.CORBA.NO_IMPLEMENT();
    }

    public boolean is_descendant_transaction(Coordinator tc){
        throw new org.omg.CORBA.NO_IMPLEMENT();
    }

    public boolean is_top_level_transaction(){
        return true;
    }

    public int hash_transaction(){
        return hash_code;
    }

    public int hash_top_level_tran(){
        throw new org.omg.CORBA.NO_IMPLEMENT();
    }

    public RecoveryCoordinator register_resource(Resource r) throws Inactive{
        synchronized(stat_semaphore){
            if (status == Status._StatusMarkedRollback){
                throw new org.omg.CORBA.TRANSACTION_ROLLEDBACK();
            }
            if (status != Status._StatusActive){
                throw new Inactive();
            }
            resources.addElement(r);
            votes.addElement(null);
        }
        return null;
    }

    public void register_synchronization(Synchronization sync)
        throws Inactive, SynchronizationUnavailable{
        synchronized(stat_semaphore){
            if (status == Status._StatusMarkedRollback){
                throw new org.omg.CORBA.TRANSACTION_ROLLEDBACK();
            }
            if (status != Status._StatusActive){
                throw new Inactive();
            }
            syncs.addElement(sync);
        }
    }

    public void register_subtran_aware(SubtransactionAwareResource r)
        throws Inactive, NotSubtransaction{
        throw new org.omg.CORBA.NO_IMPLEMENT();
    }

    public void rollback_only() throws Inactive{
        if (!move_to_state(Status._StatusMarkedRollback)){
            throw new Inactive();
        }
    }

    public String get_transaction_name(){
        return "Transaction_" + transaction_id;
    }

    public Control create_subtransaction()
        throws SubtransactionsUnavailable, Inactive{
        throw new org.omg.CORBA.NO_IMPLEMENT();
    }

    public PropagationContext get_txcontext() throws Unavailable{
        throw new org.omg.CORBA.NO_IMPLEMENT();
    }

    public Terminator get_terminator() throws Unavailable{
        if (status == Status._StatusNoTransaction){
            throw new Unavailable();
        }
        return term_ref;
    }

    public Coordinator get_coordinator() throws Unavailable{
        if (status == Status._StatusNoTransaction){
            throw new Unavailable();
        }
        return coord_ref;
    }

    private void forget(){
        resources.removeAllElements();
        votes.removeAllElements();
        syncs.removeAllElements();
    }

    public void commit(boolean report_heuristics) throws HeuristicMixed,
        HeuristicHazard{
        if (!move_to_state(Status._StatusPreparing)){
            if (move_to_state(Status._StatusRollingBack)){
                TransactionService.get_timer().kill_channel(this);
                rolling_to_back();
                throw new org.omg.CORBA.TRANSACTION_ROLLEDBACK();
            }
            throw new org.omg.CORBA.INTERNAL();
        }

        TransactionService.get_timer().kill_channel(this);

        for (int i = 0;i < syncs.size();i++){
            Synchronization sync = (Synchronization)syncs.elementAt(i);
            sync.before_completion();
        }

        if (resources.size() == 1){

            Resource r = (Resource)resources.elementAt(0);
            try {
                r.commit_one_phase();
	    } catch(org.omg.CORBA.TRANSACTION_ROLLEDBACK tr) {
              // the only one resource requested a rollback
	      votes.setElementAt(Vote.VoteRollback,0);
	      rollback();
	      throw new org.omg.CORBA.TRANSACTION_ROLLEDBACK();
            } catch(HeuristicHazard hh) {
                throw new org.omg.CORBA.NO_IMPLEMENT();
            }
	    votes.setElementAt(Vote.VoteCommit,0);

            if (!move_to_state(Status._StatusPrepared)){
                throw new org.omg.CORBA.INTERNAL();
            }
            if (!move_to_state(Status._StatusCommitting)){
                throw new org.omg.CORBA.INTERNAL();
            }
        } else {
            for (int i = 0;i < resources.size();i++){
                Resource r = (Resource)resources.elementAt(i);
                try {
                    Vote v = r.prepare();
                    votes.setElementAt(v, i);
                    if (v.value() == Vote._VoteRollback){

                        rollback();
                        throw new org.omg.CORBA.TRANSACTION_ROLLEDBACK();

                    }
                } catch(HeuristicHazard hh) {
                    throw new org.omg.CORBA.NO_IMPLEMENT();
                } catch(HeuristicMixed hm) {
                    throw new org.omg.CORBA.NO_IMPLEMENT();
                }
            }

            if (!move_to_state(Status._StatusPrepared)){
                throw new org.omg.CORBA.INTERNAL();
            }
            if (!move_to_state(Status._StatusCommitting)){
                throw new org.omg.CORBA.INTERNAL();
            }

            for (int i = 0;i < resources.size();i++){
                Resource r = (Resource)resources.elementAt(i);
                Vote     v = (Vote)votes.elementAt(i);
                
                try {
                    if (v == null){
                        throw new org.omg.CORBA.INTERNAL();
                    } else {
                        if (v.value() == Vote._VoteCommit){
                            r.commit();
                        }
                    }
                } catch(NotPrepared np) {
                    throw new org.omg.CORBA.NO_IMPLEMENT();
                } catch(HeuristicRollback hr) {
                    throw new org.omg.CORBA.NO_IMPLEMENT();
                } catch(HeuristicHazard hh) {
                    throw new org.omg.CORBA.NO_IMPLEMENT();
                } catch(HeuristicMixed hm) {
                    throw new org.omg.CORBA.NO_IMPLEMENT();
                }
            }
        }

        if (!move_to_state(Status._StatusCommitted)){
            throw new org.omg.CORBA.INTERNAL();
        }

        forget();

        if (!move_to_state(Status._StatusNoTransaction)){
            throw new org.omg.CORBA.INTERNAL();
        }
        destroy();
    }

    private void rolling_to_back(){
        for (int i = 0;i < resources.size();i++){
            Resource r = (Resource)resources.elementAt(i);
            Vote     v = (Vote)votes.elementAt(i);
            try {
                if (v == null){
                    r.rollback();
                } else {
                    if (v.value() == Vote._VoteCommit){
                        r.rollback();
                    }
                }
            } catch(HeuristicCommit hc) {
                throw new org.omg.CORBA.NO_IMPLEMENT();
            } catch(HeuristicMixed hm) {
                throw new org.omg.CORBA.NO_IMPLEMENT();
            } catch(HeuristicHazard hh) {
                throw new org.omg.CORBA.NO_IMPLEMENT();
            }
        }

        if (!move_to_state(Status._StatusRolledBack)){
            throw new org.omg.CORBA.INTERNAL();
        }

        forget();

        if (!move_to_state(Status._StatusNoTransaction)){
            throw new org.omg.CORBA.INTERNAL();
        }
        destroy();
    }

    public void rollback(){
        if (!move_to_state(Status._StatusRollingBack)){
            throw new org.omg.CORBA.TRANSACTION_REQUIRED();
        }
        TransactionService.get_timer().kill_channel(this);
        rolling_to_back();
    }
}







