/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. 
 * See the copyright.txt in the distribution for a full listing 
 * of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 * 
 * (C) 2005-2006,
 * @author JBoss Inc.
 */
/*
 * Copyright (C) 2002,
 *
 * Arjuna Technologies Limited,
 * Newcastle upon Tyne,
 * Tyne and Wear,
 * UK.
 *
 * $Id: ACCoordinator.java,v 1.5 2005/05/19 12:13:37 nmcl Exp $
 */

package com.arjuna.mwlabs.wscf.model.sagas.arjunacore;

import com.arjuna.mw.wscf.logging.wscfLogger;

import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.coordinator.*;

import com.arjuna.mw.wscf.model.sagas.participants.*;

import com.arjuna.mw.wscf.common.Qualifier;
import com.arjuna.mw.wscf.common.CoordinatorId;

import com.arjuna.mw.wsas.activity.Outcome;

import com.arjuna.mw.wsas.completionstatus.CompletionStatus;

import com.arjuna.mw.wsas.exceptions.SystemException;
import com.arjuna.mw.wsas.exceptions.WrongStateException;
import com.arjuna.mw.wsas.exceptions.ProtocolViolationException;

import com.arjuna.mw.wscf.exceptions.*;

/**
 * This class represents a specific coordination instance. It inherits from
 * ArjunaCore TwoPhaseCoordinator so we can do the prepare and commit when a
 * BA client close is requested and have it roll back if anything goes wrong.
 * The BA client cancel request maps through to TwoPhaseCoordinator cancel which
 * also does what we need. Although we inherit synchronization support from
 * TwoPhaseCoordinator it is not used by the BA code.
 *
 * This class also exposes a separate complete method which implements
 * the deprectaed BA client complete operation allowing coordinator completion
 * participants to be notified that they complete. This is pretty much redundant
 * as complete gets called at close anyway.
 * 
 * @author Mark Little (mark.little@arjuna.com)
 * @version $Id: ACCoordinator.java,v 1.5 2005/05/19 12:13:37 nmcl Exp $
 * @since 1.0.
 * 
 * @message com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_1
 *          [com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_1] -
 *          Participant failed to complete in activity {1}
 * @message com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_2
 *          [com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_2] -
 *          Null is an invalid parameter.
 * @message com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_3
 *          [com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_3] -
 *          Wrong state for operation!
 */

public class ACCoordinator extends TwoPhaseCoordinator
{

    private final static int DELISTED = 0;

    private final static int COMPLETED = 1;

    private final static int FAILED = 2;

	public ACCoordinator ()
	{
		super();

		_theId = new CoordinatorIdImple(get_uid());
	}

	public ACCoordinator (Uid recovery)
	{
		super(recovery);

		_theId = new CoordinatorIdImple(get_uid());
	}

	/**
	 * If the application requires and if the coordination protocol supports it,
	 * then this method can be used to execute a coordination protocol on the
	 * currently enlisted participants at any time prior to the termination of
	 * the coordination scope.
	 * 
	 * This implementation only supports coordination at the end of the
	 * activity.
	 * 
	 * @exception WrongStateException
	 *                Thrown if the coordinator is in a state the does not allow
	 *                coordination to occur.
	 * @exception ProtocolViolationException
	 *                Thrown if the protocol is violated in some manner during
	 *                execution.
	 * @exception SystemException
	 *                Thrown if any other error occurs.
	 * 
	 * @return The result of executing the protocol, or null.
	 */

	public Outcome coordinate (CompletionStatus cs) throws WrongStateException,
			ProtocolViolationException, SystemException
	{
		return null;
	}

    /**
     * ensure all ParticipantCompletion participants have completed and then send a complete message to
     * any remaining CoordinatorCompletion participants.
     * @throws WrongStateException if the transaction is neither RUNNING nor PREPARED
     * @throws SystemException if there are incomplete ParticipantCompletion participants or if one of the
     * CoordinatorCompletion participants fails to complete.
     */
    public synchronized void complete () throws WrongStateException,
            SystemException
	{
        int status = status();

        if (status == ActionStatus.RUNNING)
		{
            // check that all ParticipantCompletion participants have completed
            // throwing a wobbly if not

            if (pendingList != null)
            {
                RecordListIterator iter = new RecordListIterator(pendingList);
                AbstractRecord absRec = iter.iterate();

                while (absRec != null)
                {
                    if (absRec instanceof ParticipantRecord)
                    {
                        ParticipantRecord pr = (ParticipantRecord) absRec;

                        if (!pr.complete()) {

                            // ok, we must force a rollback

                            preventCommit();

                            wscfLogger.arjLoggerI18N.warn("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_1", new Object[] { get_uid() });

                            throw new SystemException("Participant failed to complete");
                        }
                    }

                    absRec = iter.iterate();
                }
            }
		}
		else
        {
            throw new WrongStateException();
        }
    }

    /**
     * close the activity
     * @return
     * @throws SystemException
     */
    public int close () throws SystemException
    {
        // we need to make sure all coordinator completion aprticipants ahve completed
        try {
            complete();
        } catch (Exception e) {
            // carry on as end will catch any further problems
        }

        // now call end(). if any of the participant completion participants are not
        // completed they will throw a WrongStateException during prepare leading to rollback

        return end(true);
    }

	/**
	 * Enrol the specified participant with the coordinator associated with the
	 * current thread.
	 * 
	 * @exception WrongStateException
	 *                Thrown if the coordinator is not in a state that allows
	 *                participants to be enrolled.
	 * @exception DuplicateParticipantException
	 *                Thrown if the participant has already been enrolled and
	 *                the coordination protocol does not support multiple
	 *                entries.
	 * @exception InvalidParticipantException
	 *                Thrown if the participant is invalid.
	 * @exception SystemException
	 *                Thrown if any other error occurs.
	 */

	public void enlistParticipant (Participant act) throws WrongStateException,
			DuplicateParticipantException, InvalidParticipantException,
			SystemException
	{
		if (act == null) throw new InvalidParticipantException();

		AbstractRecord rec = new ParticipantRecord((Participant)act, new Uid());

		if (add(rec) != AddOutcome.AR_ADDED) throw new WrongStateException();
		else
		{
			/*
			 * Presume nothing protocol, so we need to write the intentions list
			 * every time a participant is added.
			 */
		}
	}

	/**
	 * Remove the specified participant from the coordinator's list. this is the target of the
	 * 
	 * @exception InvalidParticipantException
	 *                Thrown if the participant is not known of by the
	 *                coordinator.
	 * @exception WrongStateException
	 *                Thrown if the state of the coordinator does not allow the
	 *                participant to be removed
	 * @exception SystemException
	 *                Thrown if any other error occurs.
	 */

	public synchronized void delistParticipant (String participantId)
			throws InvalidParticipantException, WrongStateException,
			SystemException
	{
		if (participantId == null)
			throw new SystemException(
					wscfLogger.log_mesg
							.getString("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_2"));

        int status = status();
        // exit is only legitimate when the TX is in these states
        switch (status) {
            case ActionStatus.RUNNING:
            case ActionStatus.ABORT_ONLY:
                changeParticipantStatus(participantId, DELISTED);
                break;
            default:
                throw new WrongStateException(
					wscfLogger.log_mesg
							.getString("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_3"));
        }
    }

	public synchronized void participantCompleted (String participantId)
			throws InvalidParticipantException, WrongStateException,
			SystemException
	{
		if (participantId == null)
			throw new SystemException(
					wscfLogger.log_mesg
							.getString("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_2"));

        int status = status();
        // completed is only legitimate when the TX is in these states
        switch (status) {
            case ActionStatus.RUNNING:
            case ActionStatus.ABORT_ONLY:
                changeParticipantStatus(participantId, COMPLETED);
                break;
            default:
                throw new WrongStateException(
					wscfLogger.log_mesg
							.getString("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_3"));
        }
	}

	public synchronized void participantFaulted (String participantId)
			throws InvalidParticipantException, SystemException
	{
		if (participantId == null)
			throw new SystemException(
					wscfLogger.log_mesg
							.getString("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_2"));

        int status = status();
        // faulted is only legitimate when the TX is in these states
        switch (status) {
            case ActionStatus.RUNNING:
                // if a participant notifies this then we need to mark the transaction as abort only
                preventCommit();
                // !!! deliberate drop through !!!
            case ActionStatus.ABORT_ONLY:
            case ActionStatus.COMMITTING:
            case ActionStatus.COMMITTED:    // this can happen during recovery processing
            case ActionStatus.ABORTING:
                changeParticipantStatus(participantId, FAILED);
                break;
            default:
                throw new SystemException(
					wscfLogger.log_mesg
							.getString("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_3"));
        }
    }

    // n.b. this is only appropriate for the 1.1 protocol

    public synchronized void participantCannotComplete (String participantId)
            throws InvalidParticipantException, WrongStateException, SystemException
    {
        if (participantId == null)
            throw new SystemException(
                    wscfLogger.log_mesg
                            .getString("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_2"));

        int status = status();
        // cannot complete is only legitimate when the TX is in these states
        switch (status) {
            case ActionStatus.RUNNING:
                // if a participant notifies this then we need to mark the transaction as abort only
                preventCommit();
                // !!! deliberate drop through !!!
            case ActionStatus.ABORT_ONLY:
                changeParticipantStatus(participantId, DELISTED);
                break;
            default:
                throw new WrongStateException(
                    wscfLogger.log_mesg
                            .getString("com.arjuna.mwlabs.wscf.model.sagas.arjunacore.ACCoordinator_3"));
        }
    }

	/**
	 * @exception SystemException
	 *                Thrown if any error occurs.
	 * 
	 * @return the complete list of qualifiers that have been registered with
	 *         the current coordinator.
	 */

	public Qualifier[] qualifiers () throws SystemException
	{
		return null;
	}

	/**
	 * @exception SystemException
	 *                Thrown if any error occurs.
	 * 
	 * @return The unique identity of the current coordinator.
	 */

	public CoordinatorId identifier () throws SystemException
	{
		return _theId;
	}

    public String type ()
    {
        return "/StateManager/BasicAction/AtomicAction/Sagas/ACCoordinator";
    }

	private final void changeParticipantStatus (String participantId, int status)
			throws InvalidParticipantException, SystemException
	{
		/*
		 * Transaction is active, so we can look at the pendingList only.
		 */

		// TODO allow transaction status to be changed during commit - exit
		// could come in late
		boolean found = false;

		if (pendingList != null)
		{
			RecordListIterator iter = new RecordListIterator(pendingList);
			AbstractRecord absRec = iter.iterate();

			try
			{
				while ((absRec != null) && !found)
				{
					if (absRec instanceof ParticipantRecord)
					{
						ParticipantRecord pr = (ParticipantRecord) absRec;
						Participant participant = (Participant) pr.value();

						if (participantId.equals(participant.id()))
						{
							found = true;

							if (status == DELISTED) {
                                pr.delist(false);
                            } else if (status == FAILED) {
                                pr.delist(true);
                            } else {
								pr.completed();
                            }
                        }
					}

					absRec = iter.iterate();
				}
			}
			catch (Exception ex)
			{
				throw new SystemException(ex.toString());
			}
		}

		if (!found) throw new InvalidParticipantException();
	}

	private CoordinatorIdImple _theId;

}
