package multithreaded;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.transaction.SystemException;
import javax.transaction.Transaction;

import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.icatch.CompositeTransactionManager;
import com.atomikos.icatch.config.Configuration;
import com.atomikos.icatch.jta.UserTransactionManager;

public class MultiThreadedTransactionTest {
	
	private static ExecutorService threads;
	private static UserTransactionManager utm;
	private static CompositeTransactionManager ctm;
	
	@BeforeClass
	public static void initSystemComponents() throws SystemException {
		threads = Executors.newSingleThreadExecutor();
		utm = new UserTransactionManager();
		utm.init();
		ctm = Configuration.getCompositeTransactionManager();
	}
	
	private SubTransactionTerminationVerifier verifier;
	
	
	@Before
	public void init() {
		verifier = new SubTransactionTerminationVerifier();
	}

	@Test
	public void testCommit() throws Exception {
		assertNoTransactionForThread();
		utm.begin();
		CompositeTransaction subTransaction = createSubTransactionForCurrentJtaTransaction();
		Transaction transaction = utm.suspend(); //IMPORTANT: suspend transaction before starting any threads!
		Future<Void> threadResult = threads.submit(new SubTransactionLogicInSeparateThread(subTransaction));
		threadResult.get();
		utm.resume(transaction); //IMPORTANT: resume transaction only when all threads are done!
		utm.commit();
		assertSubTransactionWorkWasCommitted();
		assertNoTransactionForThread();
	}
	
	private void assertNoTransactionForThread() throws SystemException {
		Assert.assertNull(utm.getTransaction());
		Assert.assertNull(ctm.getCompositeTransaction());
	}


	@Test
	public void testCommitOfSubtransactionStillAllowsRollback() throws Exception {
		assertNoTransactionForThread();
		utm.begin();
		CompositeTransaction subTransaction = createSubTransactionForCurrentJtaTransaction();
		Transaction transaction = utm.suspend(); //IMPORTANT: suspend transaction before starting any threads!
		Future<Void> threadResult = threads.submit(new SubTransactionLogicInSeparateThread(subTransaction));
		threadResult.get();
		utm.resume(transaction); //IMPORTANT: resume transaction only when all threads are done!
		utm.rollback();
		assertSubTransactionWorkWasRolledback(); //subTransaction "commit" is still subject to parent transaction outcome
		assertNoTransactionForThread();
	}
	

	private void assertSubTransactionWorkWasRolledback() {
		Assert.assertTrue(verifier.rollbackCalled());
	}

	private void assertSubTransactionWorkWasCommitted() {
		Assert.assertTrue(verifier.commitCalled());
	}

	private CompositeTransaction createSubTransactionForCurrentJtaTransaction() {
		CompositeTransaction ret = null;
		CompositeTransaction currentJtaTransactionAsCompositeTransaction = ctm.getCompositeTransaction();
		ret = currentJtaTransactionAsCompositeTransaction.createSubTransaction();
		ret.addParticipant(verifier); //for testing purposes only
		return ret;
	}

}
