/*
 * 
 * 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.logging.Levels;
import com.sun.jini.mahalo.*;
import com.sun.jini.mahalo.log.*;
import com.sun.jini.thread.*;

import java.rmi.RemoteException;
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 vote and roll forward/back 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 PrepareAndCommitJob extends Job implements TransactionConstants {
    ServerTransaction tr;
    ClientLog log;
    ParticipantHandle handle;
    int maxtries = 5;
    
    /** Logger for operations related messages */
    private static final Logger operationsLogger = 
       TxnManagerImpl.operationsLogger;

    /** Logger for persistence related messages */
    private static final Logger persistenceLogger = 
        TxnManagerImpl.persistenceLogger;

    /**
     * Constructs a <code>PrepareAndCommitJob</code>.
     *
     *
     * @param tr The <code>Transaction</code> whose participants
     *           will be instructed to vote and roll-forward/back.
     *
     * @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 handle The array of participants which will be contacted
     *                and informed to vote and roll-forward/back.
     *
     * @see com.sun.jini.thread.TaskManager
     * @see com.sun.jini.mahalo.log.ClientLog
     * @see net.jini.core.transaction.server.TransactionParticipant
     */
    public PrepareAndCommitJob(Transaction tr, TaskManager pool,
		      WakeupManager wm, ClientLog log,
		      ParticipantHandle handle) {
	super(pool, wm);

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

	this.log = log;

	if (!(tr instanceof ServerTransaction))
	    throw new IllegalArgumentException("PrepareAndCommitJob: " +
						"PrepareAndCommitJob: " +
					"must be a ServerTransaction");

	this.tr =  (ServerTransaction) tr;

	if (handle == null)
	    throw new IllegalArgumentException("PrepareAndCommitJob: " +
						"PrepareJob: " +
					"must have participants");

	this.handle = handle;
    }


    /**
     * 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 vote, roll-forward/back
     * 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) {
        if (operationsLogger.isLoggable(Level.FINER)) {
            operationsLogger.entering(PrepareAndCommitJob.class.getName(), 
	        "doWork", new Object[] {who, param});
	}
	ParticipantHandle handle = (ParticipantHandle)param;
	TransactionParticipant par = null;

        //check if a vote already exists because it was
        //recovered from the log. In this situation,
        //we do not need to log this info since it
        //exists in the log which was used for recovery...
 
        int vote = handle.getPrepState();
 
        switch (vote) {
            case COMMITTED:
            case NOTCHANGED:
            case ABORTED:
            case PREPARED:
                if (operationsLogger.isLoggable(Level.FINER)) {
                    operationsLogger.exiting(
		        PrepareAndCommitJob.class.getName(), 
 	                "doWork", new Integer(vote));
		}
		return new Integer(vote);
        }
 
        //...otherwise, explicitly instruct the participant to
        //prepare 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) {
	        if (operationsLogger.isLoggable(Level.FINER)) {
                    operationsLogger.exiting(
			PrepareAndCommitJob.class.getName(), 
	                "doWork", new Integer(ABORTED));
		}
	        return new Integer(ABORTED);
            }
	} catch (JobException je) {
	    if (operationsLogger.isLoggable(Level.FINER)) {
                operationsLogger.exiting(PrepareAndCommitJob.class.getName(), 
	            "doWork", null);
	    }
	    return null;
	}
 
 
        //At this point, if participant is null, there
        //must be an error unpacking, so retry later
        if (par == null) {
	    if (operationsLogger.isLoggable(Level.FINER)) {
                operationsLogger.exiting(PrepareAndCommitJob.class.getName(), 
	            "doWork", null);
	    }
            return null;
        }
 
        //Here we actually need to ask the participant to
        //prepare.  Note the RemoteException causes a
        //retry. Here we only log info for the cases
        //where a final outcome is available.
 
        Object response = null;
 
        try {
            vote = par.prepareAndCommit(tr.mgr, tr.id);
            response = new Integer(vote);
        } catch (TransactionException bte) {
            vote = ABORTED;
            response = new Integer(vote);
        } catch (RemoteException re) {
        } catch (RuntimeException rte) {
	    vote = ABORTED;
	    response = new Integer(vote);
	}

        if (response != null) {
	    handle.setPrepState(vote);
            try {
                log.write( new PrepareAndCommitRecord(handle, vote));
            } catch (com.sun.jini.mahalo.log.LogException le) {
                //the full package name used to disambiguate
                //the LogException
                if (persistenceLogger.isLoggable(Level.WARNING)) {
                    persistenceLogger.log(Level.WARNING,
	            "Problem writing PrepareAndCommitRecord.", le);
	        }
//TODO - ignore?		
            }
            if (operationsLogger.isLoggable(Level.FINER)) {
                operationsLogger.exiting(PrepareAndCommitJob.class.getName(), 
	            "doWork", response);
	    }

	    return response;
        }
	
        if (operationsLogger.isLoggable(Level.FINER)) {
            operationsLogger.exiting(PrepareAndCommitJob.class.getName(), 
	        "doWork", null);
	}

	return null;
    }


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

	tmp[0] = new ParticipantTask(getPool(), getMgr(), this, handle);

	return tmp;
    }


    /**
     * Gathers partial results submitted by tasks and produces
     * a single outcome.
     *
     * @see com.sun.jini.mahalo.Job
     */
    Object computeResult() throws JobException {
        if (operationsLogger.isLoggable(Level.FINER)) {
            operationsLogger.entering(PrepareAndCommitJob.class.getName(), 
	        "computeResult");
	}
	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 prepstate = NOTCHANGED;

	prepstate = ((Integer)results[0]).intValue();

        Integer result = new Integer(prepstate);
        if (operationsLogger.isLoggable(Level.FINER)) {
            operationsLogger.exiting(PrepareAndCommitJob.class.getName(), 
	        "computeResult", result);
	}
	return result;
    }
}
