/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * This software is the proprietary information of Sun Microsystems, Inc.
 * Use is subject to license terms.
 * 
 * Copyright 2004 Sun Microsystems, Inc.  Tous droits réservés.
 * Ce logiciel est propriété de Sun Microsystems, Inc.
 * Distribué par des licences qui en restreignent l'utilisation.
 *
 * ident        "@(#)MfTransactionImpl.java 1.25     04/10/06 SMI"
 *
 */

package com.sun.mfwk.trans; 

import java.util.*;
import java.util.logging.*;
import com.sun.mfwk.util.instrum.MfCpuTime;


/**
 * Implementation of the MfTransaction interface
 */


class MfTransactionImpl extends MfGenericTransactionImpl implements MfTransaction {


    public MfTransactionImpl (MfTransactionDefinition def, MfTransaction parent) {
	
	super(def, (MfGenericTransaction) parent);

	logger = getLogger();

	// Check and set thread cpuTime instrumentation
	try {
	    cpuTimeHandler = new MfCpuTime(logger);
	    
	    if ( cpuTimeHandler.isCurrentThreadCpuTimeSupported()) {
		if (!cpuTimeHandler.isThreadCpuTimeEnabled()) {
		    cpuTimeHandler.setThreadCpuTimeEnabled(true);
		    logger.fine("ThreadCpuTime enabled");
		} else {
		    logger.fine("ThreadCpuTime was already enabled.");                    }
	    } else {
		logger.warning("CurrentThreadCpuTime not supported in this " +
			       "Java Virtual Machine " +
			       System.getProperty("java.version") + " from " +
			       System.getProperty("java.vendor"));
	    }
	} catch(Exception e) {
	    logger.warning(e.toString());
	}	

	metrics = getMetricsMBean();
    }

    public synchronized int start() {
	logger.entering("MfTransactionImpl", "start");

	this.setErrorCode(MfConstants.OK);

	// Check if the transition is valid vs the state diagram
	if (getState() != POOL) {
	    if (getState() == DISABLED) {
		setErrorCode(MfConstants.MONITORING_DISABLED);
		logger.finest("MONITORING_DISABLED");		
	    } else {
		setErrorCode(MfConstants.INVALID_TRANSITION);
		logger.finest("INVALID_TRANSITION : " + "state != POOL");
	    }
	    return MfConstants.NOT_OK;
	}
	if (hasParent()) {
	    // this is a sub-transaction
	    int parentState = ((MfTransactionImpl)getParentTrans()).getState();
	    if ((parentState != RUNNING) && (parentState != BLOCKED)) {
		setErrorCode(MfConstants.INVALID_TRANSITION);
		logger.finest("INVALID_TRANSITION : " +
			      "(parentState != RUNNING) && (parentState != BLOCKED)");
		return MfConstants.NOT_OK;
	    }
	}

	startTime = 0;
	startCpuTime = 0;
	cpuTime = 0;
	blockStartCpuTime = 0;
	blockDurationCpuTime = 0;
	startOrUnblockThread = null;

	setState(RUNNING);

	synchronized (metrics) {
	    metrics.setNbInRequests(metrics.getNbInRequests() + 1);
	}

	if (hasComputingInfo())
	    startTime = java.lang.System.currentTimeMillis();

	if (cpuTimeHandler.isCurrentThreadCpuTimeSupported() && hasComputingInfo()) {
	    startCpuTime = cpuTimeHandler.getCurrentThreadCpuTime() / 1000000;
	}

	// Save thread
	if (hasComputingInfo())
	    startOrUnblockThread = Thread.currentThread();

	return MfConstants.OK;
    }

    public synchronized int stop() {
	return stop(MfConstants.STATUS_GOOD);
    }

    public synchronized int stop(int pstatus) {
	logger.entering("MfTransactionImpl", "stop", new Integer(pstatus));

	this.setErrorCode(MfConstants.OK);

	// Check if the transition is valid vs the state diagram
	if ((getState() != RUNNING) && (getState() != BLOCKED)) {
	    if (getState() == DISABLED) {
		setErrorCode(MfConstants.MONITORING_DISABLED);
		logger.finest("MONITORING_DISABLED");		
	    } else {
		setErrorCode(MfConstants.INVALID_TRANSITION);
		logger.finest("INVALID_TRANSITION : " + 
			      "(state != RUNNING) && (state != BLOCKED)");
	    }
	    return MfConstants.NOT_OK;
	}

	// Check we are in the same thread than start() or unblock()
	if (hasComputingInfo() && (Thread.currentThread().equals(startOrUnblockThread)) == false) {
	    setErrorCode(MfConstants.INVALID_CALLER_THREAD);
	    logger.finest("INVALID_CALLER_THREAD");		
	    return MfConstants.NOT_OK;
	}
	    
	if (isParent()) {
	    for (Enumeration e=getSubTransactions().elements(); e.hasMoreElements();) {
		MfTransactionImpl subTrans = (MfTransactionImpl) e.nextElement();
		if ((subTrans.getState() != WAITING_PARENT) &&
		    (subTrans.getState() != POOL)) {
		    setErrorCode(MfConstants.INVALID_TRANSITION);
		    logger.finest("INVALID_TRANSITION : " + 
				  "(subTrans.getState() != WAITING_PARENT) && (subTrans.getState() != POOL)");
		    return MfConstants.NOT_OK;
		}
	    }
	}

	status = pstatus;

	// responseTime computing
	if (hasComputingInfo()) {
	    stopTime = java.lang.System.currentTimeMillis();
	    long responseTime = stopTime - startTime;
	    synchronized (metrics) {
		metrics.setAccumulatedResponseTime(metrics.getAccumulatedResponseTime() + responseTime);
		metrics.setAccumulatedSqResponseTime(metrics.getAccumulatedSqResponseTime() + (responseTime*responseTime));
	    }
	    updateMinMax(responseTime, RESPONSE_TIME);
	}
    
	// cpuTime computing
	if (hasComputingInfo() && cpuTimeHandler.isCurrentThreadCpuTimeSupported()) {
	    long stopCpuTime = cpuTimeHandler.getCurrentThreadCpuTime() / 1000000;
	    if (getState() == BLOCKED) {
		// Handle the case of implicit unblock (stop done from a BLOCKED state)
		blockDurationCpuTime += stopCpuTime - blockStartCpuTime;
	    }
	    cpuTime = (stopCpuTime - startCpuTime) - blockDurationCpuTime;
	}

	if (hasComputingInfo() && !isParent()) {
	    // This is NOT a parent transaction
	    synchronized (metrics) {
		if (status == MfConstants.STATUS_FAILED)
		    metrics.setNbFailedRequests(metrics.getNbFailedRequests() + 1);
		else if (status == MfConstants.STATUS_ABORT)
		    metrics.setNbAbortedRequests(metrics.getNbAbortedRequests() + 1);
		else
		    metrics.setNbOutRequests(metrics.getNbOutRequests() + 1);		    

		// cpuTime setting
		if (cpuTimeHandler.isCurrentThreadCpuTimeSupported()) {
		    metrics.setAccumulatedServiceTime(metrics.getAccumulatedServiceTime() + cpuTime);
		    metrics.setAccumulatedSqServiceTime(metrics.getAccumulatedSqServiceTime() + (cpuTime*cpuTime));
	    
		}
	    }

	    updateMinMax(cpuTime, SERVICE_TIME);
	
	    if (hasParent())
		setState(WAITING_PARENT);
	    else
		setState(POOL);

	} else {
	    // This is a parent transaction

	    // Initialize the correlated metrics to the ones of the parent (ie this) transaction
	    int corrStatus = status;
	    long corrCpuTime;
	    if (hasComputingInfo()) {
		corrCpuTime = cpuTime;
	    } else {
		corrCpuTime = 0;
	    }

	    long corrStartTime = 0;
	    long corrStopTime = 0;
	    for (Enumeration enumer=getSubTransactions().elements(); enumer.hasMoreElements();) {
		// Add to this transaction cpuTime, the cpuTime of all the sub-transactions 
		// that are in WAITING_PARENT state
		MfTransactionImpl subTrans = (MfTransactionImpl) enumer.nextElement();
		if (subTrans.getState() == WAITING_PARENT) {
		    if (cpuTimeHandler.isCurrentThreadCpuTimeSupported())
			corrCpuTime += subTrans.getCpuTime();
		    // Check if at least one of the sub-transactions has failed or aborted
		    if (subTrans.status == MfConstants.STATUS_FAILED)
			corrStatus = MfConstants.STATUS_FAILED;
		    else if (subTrans.status == MfConstants.STATUS_ABORT)
			corrStatus = MfConstants.STATUS_ABORT;
		}
		if (!hasComputingInfo()) {
		    // It is a parent transaction without any own metrics
		    // To be able to compute the responsetime from the sub-transactions,
		    // find the first started transaction and the last stopped one.
		    long subStartTime = subTrans.getStartTime();
		    long subStopTime = subTrans.getStopTime();
		    if (corrStartTime == 0 && corrStopTime == 0) {
			corrStartTime = subStartTime;
			corrStopTime = subStopTime;
		    } else {
			if (subStartTime < corrStartTime) 
			    corrStartTime = subStartTime;
			if (subStopTime > corrStopTime)
			    corrStopTime = subStopTime;
		    }		    
		}
	    } // for

	    // Update the correlated metrics
	    synchronized (metrics) {
		if (corrStatus == MfConstants.STATUS_FAILED)
		    metrics.setNbFailedRequests(metrics.getNbFailedRequests() + 1);
		else if (corrStatus == MfConstants.STATUS_ABORT)
		    metrics.setNbAbortedRequests(metrics.getNbAbortedRequests() + 1);
		else
		    metrics.setNbOutRequests(metrics.getNbOutRequests() + 1);

		if (cpuTimeHandler.isCurrentThreadCpuTimeSupported()) {
		    metrics.setAccumulatedServiceTime(metrics.getAccumulatedServiceTime() + corrCpuTime);
		    metrics.setAccumulatedSqServiceTime(metrics.getAccumulatedSqServiceTime() + (corrCpuTime*corrCpuTime));
		}

		// if !hasComputingInfo(), compute the responseTime
		if (!hasComputingInfo()) {
		    long corrResponseTime = corrStopTime - corrStartTime;
		    metrics.setAccumulatedResponseTime(metrics.getAccumulatedResponseTime() + corrResponseTime);
		    metrics.setAccumulatedSqResponseTime(metrics.getAccumulatedSqResponseTime() + (corrResponseTime*corrResponseTime));
		    updateMinMax(corrResponseTime, RESPONSE_TIME);
		}
	    }

	    updateMinMax(corrCpuTime, SERVICE_TIME);

	    // Update the metrics related to the parent (ie this) transaction only
	    if (hasComputingInfo()) {
		synchronized (metrics) {
		    if (status == MfConstants.STATUS_FAILED)
			metrics.setSingleNbFailedRequests(metrics.getSingleNbFailedRequests() + 1);
		    else if (status == MfConstants.STATUS_ABORT)
			metrics.setSingleNbAbortedRequests(metrics.getSingleNbAbortedRequests() + 1);

		    if (cpuTimeHandler.isCurrentThreadCpuTimeSupported()) {
			metrics.setSingleAccumulatedServiceTime(metrics.getSingleAccumulatedServiceTime() + cpuTime);
			metrics.setSingleAccumulatedSqServiceTime(metrics.getSingleAccumulatedSqServiceTime() + (cpuTime*cpuTime));
			updateMinMax(cpuTime, SINGLE_SERVICE_TIME);
		    }
		}
	    }


	    // Update the parent transaction (this) and its sub-transactions states
	    for (Enumeration enumer=getSubTransactions().elements(); enumer.hasMoreElements();) {
		((MfTransactionImpl) enumer.nextElement()).setState(POOL);
	    }
	    setState(POOL);
	}


	return MfConstants.OK;
    }

    public synchronized int block() {
	logger.entering("MfTransactionImpl", "block");

	this.setErrorCode(MfConstants.OK);

	if (!hasComputingInfo()) {
	    // no effect if the transaction has not any own metrics
	    logger.finest("Calling a block() on a transaction without any own metrics has no effect!");
	    return MfConstants.OK;
	}

	if (getState() != RUNNING) {
	    if (getState() == DISABLED) {
		setErrorCode(MfConstants.MONITORING_DISABLED);
		logger.finest("MONITORING_DISABLED");		
	    } else {
		setErrorCode(MfConstants.INVALID_TRANSITION);
		logger.finest("INVALID_TRANSITION : " +
			      "state != RUNNING");
	    }
	    return MfConstants.NOT_OK;
	}

	// Check we are in the same thread than start() or unblock()
	if ((Thread.currentThread().equals(startOrUnblockThread)) == false) {
	    setErrorCode(MfConstants.INVALID_CALLER_THREAD);
	    logger.finest("INVALID_CALLER_THREAD");		
	    return MfConstants.NOT_OK;
	}
	    
	setState(BLOCKED);

	if (cpuTimeHandler.isCurrentThreadCpuTimeSupported()) {
	    blockStartCpuTime = cpuTimeHandler.getCurrentThreadCpuTime() / 1000000;
	}

	return MfConstants.OK;
    }

    public synchronized int unblock() {
	logger.entering("MfTransactionImpl", "unblock");

	this.setErrorCode(MfConstants.OK);

	if (!hasComputingInfo()) {
	    // no effect if the transaction has not any own metrics
	    logger.finest("Calling an unblock() on a transaction without any own metrics has no effect!");
	    return MfConstants.OK;
	}

	if (getState() != BLOCKED) {
	    if (getState() == DISABLED) {
		setErrorCode(MfConstants.MONITORING_DISABLED);
		logger.finest("MONITORING_DISABLED");		
	    } else {
		setErrorCode(MfConstants.INVALID_TRANSITION);
		logger.finest("INVALID_TRANSITION : " + "state != BLOCKED");
	    }
	    return MfConstants.NOT_OK;
	}
	    
	setState(RUNNING);

	if (cpuTimeHandler.isCurrentThreadCpuTimeSupported()) {
	    long blockStopCpuTime = cpuTimeHandler.getCurrentThreadCpuTime() / 1000000;
	    blockDurationCpuTime += blockStopCpuTime - blockStartCpuTime;
	}

	// Save thread reference
	startOrUnblockThread = Thread.currentThread();

	return MfConstants.OK;
    }

    protected long getCpuTime() {
	return cpuTime;
    }

    protected long getStartTime() {
	return startTime;
    }

    protected long getStopTime() {
	return stopTime;
    }


    private int status = 0;
    private MfTransactionMetrics metrics = null;
    private long startTime = 0;
    private long stopTime = 0;
    private long startCpuTime = 0;
    private long cpuTime = 0;
    private long blockStartCpuTime = 0;
    private long blockDurationCpuTime = 0;
    private MfCpuTime cpuTimeHandler = null;
    private Logger logger = null;
    private Thread startOrUnblockThread = null;
}
