/**
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 -
 - JASS: Java Advanced tranSaction Support
 -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 -
 - This module was originally developed by 
 -
 -    LSD (Distributed Systems Lab, http://lsd.ls.fi.upm.es/lsd/lsd.htm)
 -    at Universidad Politecnica de Madrid (UPM) as an ObjectWeb Consortium
 -    (http://www.objectweb.org) project.
 -
 -    This project has been partially funded by the European Commission under
 -    the IST programme of V FP grant IST-2001-37126 and by the Spanish
 -    Ministry of Science & Technology (MCyT) grants TIC2002-10376-E and
 -    TIC2001-1586-C03-02
 -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 -  The original code and portions created by LSD are 
 -  Copyright (c) 2004 LSD (UPM)  
 -  All rights reserved.
 -  
 - Redistribution and use in source and binary forms, with or without 
 - modification, are permitted provided that the following conditions are met:
 -
 - -Redistributions of source code must retain the above copyright notice, this
 - list of conditions and the following disclaimer. 
 -
 - -Redistributions in binary form must reproduce the above copyright notice, 
 - this list of conditions and the following disclaimer in the documentation 
 - and/or other materials provided with the distribution. 
 -
 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 - POSSIBILITY OF SUCH DAMAGE.
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 -
 -  Author: Francisco Perez Sorrosal (frperezs)
 -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

package org.objectweb.jass.hls.ont;

import org.apache.log4j.Logger;

import javax.activity.*;
import javax.activity.Status;
import javax.activity.SystemException;
import javax.activity.opennested.*;
import javax.naming.*;
import javax.transaction.*;

import java.io.Serializable;
import java.util.Stack;

/**
 * ONT service main class. It implements the UserOpenNested service 
 * interface offered to applications throught JNDI by the 
 * {@link org.objectweb.jass.hls.ont.jboss.ONTService} class. A singleton ONT
 * object is created.
 * @author fran
 * Date: Feb 11, 2004
 * org.objectweb.jass.hls.ontONT.java
 */
public class ONT implements UserOpenNested, Serializable {

	private static Logger log = Logger.getLogger(ONT.class);
	
	// Constants --------------------------------------------------------------

	// Attributes ---------------------------------------------------------------

	private static ONT singleton = new ONT();

	// The ONT service manager
	private ONTServiceManager serviceManager;
	// This instance provides access to the AS...
	private ActivityManager activityManager = null;
	// ...and this one to JTA.
	private TransactionManager tm = null;
	private int defaultTimeout = 0; // It means no timeout

	// Constructors -----------------------------------------------------------

	/**
	 * Default Constructor. Creates the singleton ONT instance, allowing
	 * access to AS and JTA, and registers the ONT service manager
	 * object in the AS. 
	 */
	private ONT() {

		try {
			Context ctx = new InitialContext();

			log.info(
				getThreadName() + " Looking for an ActivityManager instance...");
			activityManager = (ActivityManager) ctx.lookup("ActivityManager");
			log.info(getThreadName() + " AM instance found!!!");

			log.info(getThreadName() + " Looking for a TransactionManager...");
			tm = ((TransactionManager) ctx.lookup("java:/TransactionManager"));
			log.info(" TM found !!!");

			// The ONT service manager is created and registered in AS instance
			serviceManager = new ONTServiceManager();
			activityManager.registerService(serviceManager);

			log.info(
				getThreadName()
					+ " ONT service registered in Activity Service instance");
		} catch (NamingException e) {
			log.error("Instance NOT found");
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// Static -----------------------------------------------------------------

	/**
	 * Returns the unique ONT service instance.
	 * @return The unique instance.
	 */
	public static ONT getSingleton() {

		return singleton;
	}

	// Public ---------------------------------------------------------------------

	// UserOpenNested implementation ------------------------------------------

	/**
	 * A new Activity is created. If the invoking thread already has an active
	 * Activity associated with it then the newly created Activity will be 
	 * nested within it. Regardless of whether or not the Activity is nested, a 
	 * top-level transaction is created using the Transaction Service and 
	 * associated with the newly created Activity. The invoking thread's notion 
	 * of the current Activity will be changed to this Activity.
	 * 
	 * @param timeout - is used to control the lifetime of the transactional 
	 * Activity. If the Activity has not completed by the time timeout seconds 
	 * elapses, then it is subject to being rolled back. The timeout defined by 
	 * the Open Nested Current interface is not controlled by the Open 
	 * Nested Service which rather relies on the underlying Activity Service 
	 * to manage it. Values the timeout can have are those defined by the 
	 * Activity Service.
	 */
	public void activityBegin(int timeout)
		throws InvalidActivityException, TimeoutRangeException {

		ThreadInfo ti = getThreadInfo();

		log.info(getThreadName() + " BEGINING ONT ACTIVITY...");

		try {
			// We suspend any possible active transaction
			if (tm.getTransaction() != null) {
				log.info("\t" + tm.getTransaction() + " is suspending...");
				ti.suspendedTxs.push(tm.suspend());
			} else
				log.info("\tThere are no transactions to suspend");

			log.info("\tTransactions in stack: " + ti.suspendedTxs.size());
			if (timeout == 0)
				activityManager.begin(defaultTimeout);
			else
				activityManager.begin(timeout);
			// We start the associated transaction
			tm.begin();
			log.info("\t" + tm.getTransaction() + " started");
		} catch (InvalidStateException e) {
			throw new InvalidActivityException();
		} catch (TimeoutRangeException e) {
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
		}
		log.info(getThreadName() + " ONT ACTIVITY BEGUN");
		log.info(
			"\n----------------------------------------------------------------------------------\n");
	}

	/**
	 * The transactional Activity associated with the client thread is committed;
	 * this implicitly causes the commit of the associated transaction. Only the 
	 * Activity originator may call activity_commit().
	 * 
	 * @param compensator_object - If the Activity/transaction can commit, 
	 * this object will be registered as the compensating action for this 
	 * activity/transaction. Failure to register the Compensator will cause the 
	 * Activity to rollback.
	 */
	public void activityCommit(Compensator compensator_object)
		throws
			NoActivityException,
			HeuristicMixedException,
			HeuristicRollbackException,
			ActivityPendingException,
			ContextPendingException,
			NotOriginatorException,
			ActivityRolledBackException,
			HeuristicCompensateException,
			HeuristicNoCompensateException {

		Outcome out = null;
		ThreadInfo ti = getThreadInfo();

		log.info(getThreadName() + " COMMITTING ONT ACTIVITY...");

		try {
			// If there were a disaster
			if (activityManager.getCompletionStatus()
				== CompletionStatus.CompletionStatusFailOnly) {
				log.info("\t" + tm.getTransaction() + " is rolling-back...");
				tm.rollback();
				log.info("\t Transaction rolled-back !!!!!");
				out = activityManager.complete();
				throw new ActivityRolledBackException();
			} else {
				log.info("\t " + tm.getTransaction() + " is committing...");
				tm.commit();
				log.info("\t Transaction committed !!!!!");
				if (compensator_object != null) {
					CompensatingAction action =
						new CompensatingAction(compensator_object, 0);
					ActivityCoordinator parent =
						activityManager.getParentCoordinator();
					// If it is a nested activity
					if (parent != null) {
						try {
							// We try to register the compensator object with the
							// parent activity
							parent.addAction(action, ONTCompletionSS.COMPLETION_SS_NAME, 0);
							log.debug(
								"Compensator added sucessfully with parent ("
									+ parent.getName()
									+ ")");
						} catch (Exception e) {
							log.info(
								"Action not added to the parent. Compensating...");
							compensator_object.compensate();
							log.info(
								"Triying to complete activity "
									+ getActivityName()
									+ " with CompletionStatusFail *****");
							out =
								activityManager.completeWithStatus(
									CompletionStatus.CompletionStatusFail);
							throw new ActivityRolledBackException();
						}
					} else
						log.info(
							"There is no parent activity to register the compensator!");
				}
				log.info(
					"Triying to complete activity "
						+ getActivityName()
						+ " with CompletionStatusSuccess *****");
				out =
					activityManager.completeWithStatus(
						CompletionStatus.CompletionStatusSuccess);
				if (out != null)
					if (out.getName().equals("heuristic_compensate_decision"))
						throw new HeuristicCompensateException();
			}
		} catch (RollbackException e) {
			try {
				log.info("\t Transaction Rolledback !!!!!");
				log.info(
					" Triying to complete activity "
						+ getActivityName()
						+ " with CompletionStatusFail *****");
				out =
					activityManager.completeWithStatus(
						CompletionStatus.CompletionStatusFail);
				if (out == null) {
					throw new ActivityRolledBackException();
				} else {
					if (out.getName().equals("heuristic_cannot_compensate"))
						throw new HeuristicNoCompensateException();
				}
			} catch (Exception e1) {
				e1.printStackTrace();
			}
		} catch (ActivityRolledBackException e) {
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			log.info(getThreadName() + " ONT ACTIVITY COMPLETED");
			// If there is a suspended transaction, we must to resume it
			if (!ti.suspendedTxs.empty()) {
				try {
					tm.resume((Transaction) ti.suspendedTxs.pop());
					log.info("\t" + tm.getTransaction() + " has been resumed");
					log.info("\tTransactions in stack: " + ti.suspendedTxs.size());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			log.info(
				"\n----------------------------------------------------------------------------------\n");
		}
	}

	/**
	 * The transactional Activity associated with the client thread is rolled 
	 * back; this implicitly causes the rollback of the associated transaction.
	 * Any nested transactional activities are also rolled back.
	 */
	public void activityRollback()
		throws NoActivityException, HeuristicNoCompensateException {

		Outcome out = null;
		ThreadInfo ti = getThreadInfo();

		log.info(getThreadName() + " ROLLING-BACK ONT ACTIVITY...");

		try {
			if (activityManager.getCoordinator() == null)
				throw new NoActivityException();

			log.info("\t" + tm.getTransaction() + " is rolling-back...");
			tm.rollback();
			log.info("\tTransaction rolled-back !!!!!");
			log.info(
				"Completing activity " + getActivityName() + " with fail *****");

			if (activityManager.getCompletionStatus()
				== CompletionStatus.CompletionStatusFailOnly)
				out = activityManager.complete();
			else
				out =
					activityManager.completeWithStatus(
						CompletionStatus.CompletionStatusFail);

			if (out != null) {
				if (out.getName().equals("heuristic_cannot_compensate"))
					throw new HeuristicNoCompensateException();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			log.info(getThreadName() + " ONT ACTIVITY COMPLETED");
			if (!ti.suspendedTxs.empty()) {
				try {
					tm.resume((Transaction) ti.suspendedTxs.pop());
					log.info("\t" + tm.getTransaction() + " has been resumed");
					log.info("\tTransactions in stack: " + ti.suspendedTxs.size());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			log.info(
				"\n----------------------------------------------------------------------------------\n");
		}
	}

	/**
	 * The current Activity is modified so that the only possible outcome is 
	 * rollback. The associated transaction is also marked as rollback only.
	 */
	public void activitySetRollbackOnly() throws NoActivityException {
		try {
			tm.setRollbackOnly();
			activityManager.setCompletionStatus(
				CompletionStatus.CompletionStatusFailOnly);
		} catch (NoActivityException e) {
			e.printStackTrace();
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * This method resets the default timeout value for ONT Activities. The 
	 * default timeout value is used for activities that are begun with a 
	 * timeout value of 0.
	 */
	public void activitySetTimeout(int seconds) throws TimeoutRangeException {
		if (seconds < -1)
			throw new TimeoutRangeException();
		defaultTimeout = seconds;
	}

	/**
	 * This operation returns the default timeout value that is used for 
	 * activities that are begun with a timeout value of 0.
	 */
	public int getActivityTimeout() {
		return defaultTimeout;
	}

	/**
	 * This operation returns the ActivityStatus of the activity associated 
	 * with the calling thread. If there is no activity associated with the 
	 * calling thread, the StatusNoActivity value is returned.
	 */
	public int activityGetStatus() {
		int status = Status.StatusNoActivity;
		try {
			status = activityManager.getStatus();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return status;
	}

	/**
	 * This operation returns a printable string describing the  current 
	 * activity. If there is no activity associated with the calling thread, 
	 * an empty string is returned.
	 */
	public String getActivityName() {
		String name = null;
		try {
			name = activityManager.getName();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return name;
	}

	/**
	 * This operation returns a printable string describing the transaction 
	 * associated with the current activity. If there is no current activity a 
	 * null string is returned.
	 */
	public String getTransactionName() {
		String name = null;
		try {
			name = tm.getTransaction().toString();
		} catch (javax.transaction.SystemException e) {
			e.printStackTrace();
		}
		return name;
	}

	// My Public --------------------------------------------------------------

	// My Private -------------------------------------------------------------

	// Local thread info
	private ThreadLocal threadActivityInfo = new ThreadLocal();

	/**
	 * Obtains the information associated with the current thread
	 * @return Information associated with the current thread
	 */
	private ThreadInfo getThreadInfo() {

		ThreadInfo ret = (ThreadInfo) threadActivityInfo.get();

		if (ret == null) {
			ret = new ThreadInfo();
			threadActivityInfo.set(ret);
		} else
			log.debug("Thread info != null");

		return ret;
	}

	/**
	 * Returns the current execution thread name.
	 * @return the current thread name.
	 */
	private String getThreadName() {
		return Thread.currentThread().getName();
	}

	// Inner classes --------------------------------------------------------------

	/**
	 * Contains the stack of the thread-associated suspended transactions. 
	 * It also has a TEST field "suspendedAct" to test the suspend/resume 
	 * methods.
	 */
	static class ThreadInfo {
		Stack suspendedTxs = new Stack();
		Stack suspendedAct = new Stack();
	}

	// Test -----------------------------------------------------------------------

	/**
	 * This is a TEST method!!!
	 * It allows to suspend the current activity.
	 * @return the ONTActivity suspended.
	 */
	public ONTActivity suspend() {
		ActivityToken at = null;
		Transaction tx = null;
		ONTActivityImpl activity= null;
				
		try {
			tx = tm.suspend();
			at = activityManager.suspend();
			activity = new ONTActivityImpl(tx, at);
		} catch (javax.transaction.SystemException e) {			
			e.printStackTrace();
		} catch (ServiceNotRegisteredException e) {			
			e.printStackTrace();
		} catch (SystemException e) {			
			e.printStackTrace();
		}			
		
/*		ThreadInfo ti = getThreadInfo();
		try {
			ti.suspendedAct.push(activityManager.suspend());
		} catch (ServiceNotRegisteredException e) {
			e.printStackTrace();
		} catch (SystemException e) {
			e.printStackTrace();
		} */
		return activity;
	}

	/**
	 * This is a TEST method!!!
	 * It allows to resume the last suspended activity.
	 * @param activity the ONTActivity to be resumed.
	 */
	public void resume(ONTActivity activity) {		
		
		if(activity != null) {
			try {
				tm.resume(activity.getTransaction());
				activityManager.resume(activity.getActivityToken());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		/*ThreadInfo ti = getThreadInfo();
		try {
			activityManager.resume((ActivityToken) ti.suspendedAct.pop());
		} catch (ServiceNotRegisteredException e) {
			e.printStackTrace();
		} catch (SystemException e) {
			e.printStackTrace();
		} catch (InvalidActivityException e) {
			e.printStackTrace();
		} catch (InvalidParentContextException e) {
			e.printStackTrace();
		}*/
	}

	// End Test ------------------------------------------------------------------

}