/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */
package com.sun.jini.mahalo;

import net.jini.core.transaction.*;
import net.jini.core.transaction.server.*;

import com.sun.jini.mahalo.*;
import com.sun.jini.mahalo.log.*;
import com.sun.jini.thread.*;

import java.rmi.activation.ActivateFailedException;
import java.rmi.RemoteException;
import java.rmi.NoSuchObjectException;
import java.rmi.MarshalException;
import java.rmi.StubNotFoundException;
import java.rmi.UnexpectedException;
import java.rmi.UnknownHostException;
import java.rmi.UnmarshalException;
import java.rmi.ConnectIOException;
import java.rmi.AccessException;
import java.rmi.ConnectException;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * An implementation of a <code>Job</code> which interacts with
 * a set of <code>TransactionParticipant</code>s to inform them
 * to roll forward changes associated with a given <code>Transaction</code>.
 *
 * @author Sun Microsystems, Inc.
 *
 * @see com.sun.jini.mahalo.Job
 * @see com.sun.jini.mahalo.ParticipantTask
 * @see net.jini.core.transaction.Transaction
 * @see net.jini.core.transaction.server.TransactionParticipant
 */
public class CommitJob extends Job implements TransactionConstants {
    ServerTransaction tr;
    ClientLog log;
    ParticipantHandle[] handles;
    int maxtries = Integer.MAX_VALUE;
    static final Logger logger = TxnManagerImpl.participantLogger;

    /**
     * Constructs an <code>CommitJob</code>
     *
     *
     * @param tr The <code>Transaction</code> whose participants
     *           will be instructed to roll-forward.
     *
     * @param pool The <code>TaskManager</code> which provides the
     *             threads used for interacting with participants.
     *
     * @param log  The <code>ClientLog</code> used for recording
     *             recovery data.
     *
     * @param handles The array of participants which will be contacted
     *                and informed to roll-forward.
     *
     * @see com.sun.jini.thread.TaskManager
     * @see com.sun.jini.mahalo.log.ClientLog
     * @see net.jini.core.transaction.server.TransactionParticipant
     */
    public CommitJob(Transaction tr, TaskManager pool,
		      WakeupManager wm, ClientLog log,
		      ParticipantHandle[] handles) {
	super(pool, wm);

	if (log == null)
	    throw new IllegalArgumentException("CommitJob: CommitJob: " +
							"log is null");

	this.log = log;

        if (!(tr instanceof ServerTransaction))
            throw new IllegalArgumentException("PrepareJob: PrepareJob: " +
                                        "must be a ServerTransaction");
 
        this.tr =  (ServerTransaction) tr;
 
        if (handles == null)
            throw new IllegalArgumentException("PrepareJob: PrepareJob: " +
                                        "must have participants");
 
        if (handles.length == 0)
            throw new IllegalArgumentException("PrepareJob: PrepareJob: " +
                                        "must have participants");
 
        this.handles = handles;
    }


    /**
     * The work to be performed by each <code>TaskManager.Task</code>
     * is provided by the <code>Job</code> that creates it.
     * The work performed by a task belonging to the CommitJob
     * contacts a participant, instructs it to roll-forward and
     * log appropriately.
     *
     * @param who The task performing the work
     *
     * @param param A parameter, of the task's choosing, useful
     *              in performing work.
     *
     * @see com.sun.jini.mahalo.Job
     * @see com.sun.jini.thread.TaskManager.Task
     */
    Object doWork(TaskManager.Task who, Object param) {
        ParticipantHandle handle = (ParticipantHandle)param;
        TransactionParticipant par = null;

        //Check to see if participant has state associated
	//with it on account of being recovered from a log

        if (logger.isLoggable(Level.FINEST)) {
            logger.log(Level.FINEST,
                "CommitJob:doWork committing handle: {0}", handle);
	}

        int vote = handle.getPrepState();
 
        switch (vote) {
	    case COMMITTED:
            case NOTCHANGED:
            case ABORTED:
                return new Integer(vote);
        }

        //Instruct the TransactionParticipant to roll forward
        //after unpacking it and checking against the
        //max retry threshold.

        if (par == null)
            par = handle.getPreParedParticipant();
 
        //If you have exhausted the max retry threshold
        //stop, so that no further attempts are made.
 
	try {
            if (attempt(who) > maxtries) {
                return new Integer(COMMITTED);
            }
	} catch (JobException je) {
	    return null;
	}
 
        //At this point, if participant is null, there
        //must be an error unpacking, so retry later
        if (par == null)
            return null;
 
 
        //Here we actually need to instruct the participant to
        //roll forward.  Note the RemoteException causes a
        //retry. Here we only log info for the cases
        //where a final outcome is available.
 
        Object response = null;
 
        try {
            par.commit(tr.mgr, tr.id);
            response = new Integer(COMMITTED);
        } catch (TransactionException bte) {
            //The participant doesn't have record of the
            //transaction, so it must have already rolled
            //forward.
            response = new Integer(COMMITTED);
	} catch (NoSuchObjectException nsoe) {
	    //No definition for object in VM, so stop
	    //and consider committed.
	    response = new Integer(COMMITTED);
	} catch (ConnectException ce) {
	    //failure setting up connection, so give
	    //participant more time by retrying
	} catch (UnknownHostException uhe) {
	    //could not resolve host for participant, so
	    //stop and consider committed
	    response = new Integer(COMMITTED);
	} catch (ConnectIOException cioe) {
	    //multiplexed connection or cached 
	    //connection problem, give participant more time
	} catch (MarshalException me) {
	    //cannot send parameters, so stop and consider done
	    response = new Integer(COMMITTED);
	} catch (AccessException ae) {
	    //Access error on registry or rmid consider done
	    response = new Integer(COMMITTED);
	} catch (ActivateFailedException afe) {
	    //Activatable Ref Stub couldn't activate
	    //participant, so stop and consider done
	    response = new Integer(COMMITTED);
        } catch (RemoteException re) {
            //Something happened with the network, so
            //return null to retry at a later time.
        } catch (RuntimeException rte) {
            //Something happened with the participant, so
            //stop and consider done
	    response = new Integer(COMMITTED);
        }
 
 
        if (response != null) {
	    handle.setPrepState(COMMITTED);
            try {
                log.write( new ParticipantCommitRecord(handle));
            } catch (com.sun.jini.mahalo.log.LogException le) {
                //the full package name used to disambiguate
                //the LogException
            }

	    return response;
        }
 
        return null;
    }


    /**
     * Creates the <code>TaskManager.Task</code>s necessary to
     * inform participants to roll-back.
     */
    TaskManager.Task[] createTasks() {
	TaskManager.Task[] tmp = new TaskManager.Task[handles.length];

	for (int i = 0; i < handles.length; i++) {
	    tmp[i] = 
	        new ParticipantTask(getPool(), getMgr(), this, handles[i]);
	}

	return tmp;
    }


    /**
     * Gathers partial results submitted by tasks and produces
     * a single outcome.
     *
     * @see com.sun.jini.mahalo.Job
     */
    Object computeResult() throws JobException {
	try {
	    if (!isCompleted(0))
	        throw new ResultNotReadyException("Cannot compute result " +
					"since there are jobs pending");
	} catch (JobNotStartedException jnse) {
	    throw new ResultNotReadyException("Cannot compute result since" +
					   " jobs were not created");
	}

	int tmp = 0;
	int count = 0;

	checkresults:
	for (int i = 0; i < results.length; i++) {
	    tmp = ((Integer)results[i]).intValue();

	    if (tmp == COMMITTED)
		count++;
	}

        if (logger.isLoggable(Level.FINEST)) {
            logger.log(Level.FINEST,
                "CommitJob:computeResult {0} participants COMMITTED", 
		new Integer(count));
	}

	return new Integer(COMMITTED);
    }
}
