/* * Copyright (C) 2006 Alfresco, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.alfresco.repo.transaction; import java.util.Random; import javax.transaction.UserTransaction; import junit.framework.TestCase; import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; /** * @see org.alfresco.repo.transaction.TransactionAwareSingleton * * @author Derek Hulley */ public class TransactionAwareSingletonTest extends TestCase { private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private static Random rand = new Random(); /** the instance to test */ private TransactionAwareSingleton singleton = new TransactionAwareSingleton(); private static final Integer INTEGER_ONE = new Integer(1); private static final Integer INTEGER_TWO = new Integer(2); private TransactionService transactionService; public void setUp() throws Exception { transactionService = (TransactionService) ctx.getBean("transactionComponent"); } public void testCommit() throws Throwable { UserTransaction txn = transactionService.getUserTransaction(); try { txn.begin(); singleton.put(INTEGER_ONE); check(INTEGER_ONE, true); check(null, false); // commit txn.commit(); } catch (Throwable e) { try { txn.rollback(); } catch (Throwable ee) {} throw e; } check(INTEGER_ONE, true); check(INTEGER_ONE, false); } public void testRollback() throws Throwable { UserTransaction txn = transactionService.getUserTransaction(); try { txn.begin(); singleton.put(INTEGER_TWO); check(INTEGER_TWO, true); check(null, false); // rollback txn.rollback(); } catch (Throwable e) { try { txn.rollback(); } catch (Throwable ee) {} throw e; } check(null, true); check(null, false); } private static final int THREAD_COUNT = 20; public void testThreadsCommit() throws Throwable { TestThread[] threads = new TestThread[THREAD_COUNT]; for (int i = 0; i < THREAD_COUNT; i++) { TestThread thread = new TestThread(true); thread.start(); threads[i] = thread; } // wait for them to complete for (int i = 0; i < THREAD_COUNT; i++) { while (threads[i].finished == false) { synchronized(this) { try { wait(20); } catch (Throwable e) {} } } if (threads[i].error != null) { throw threads[i].error; } } } public void testThreadsRollback() throws Throwable { TestThread[] threads = new TestThread[THREAD_COUNT]; for (int i = 0; i < THREAD_COUNT; i++) { TestThread thread = new TestThread(false); thread.start(); threads[i] = thread; } } /** * Dumps random values into * @author Derek Hulley */ private class TestThread extends Thread { private boolean finished = false; private Throwable error; private boolean commit; private Integer value = new Integer((int)System.nanoTime()); public TestThread(boolean commit) { this.commit = commit; } @Override public synchronized void run() { UserTransaction txn = transactionService.getUserTransaction(); try { txn.begin(); singleton.put(value); // wait for some random time try { // DH: The "+1" is necessary to ensure that wait(0) is never called wait((long)(rand.nextDouble() * 1000.0) + 1); // wait up to a second } catch (InterruptedException e) { // ignore } // check check(value, true); if (commit) { txn.commit(); } else { // rollback txn.rollback(); } } catch (Throwable e) { try { txn.rollback(); } catch (Throwable ee) {} this.error = e; } if (!commit) { try { // no thread changes check(null, false); } catch (Throwable e) { error = e; } } finished = true; } } private void check(final Integer expected, boolean inTransaction) { TransactionWork checkWork = new TransactionWork() { public Object doWork() throws Exception { Integer actual = singleton.get(); assertTrue("Values don't match: " + expected + " != " + actual, actual == expected); return null; } }; if (inTransaction) { TransactionUtil.executeInUserTransaction(transactionService, checkWork); } else { TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, checkWork); } } }