de.hybris.platform.test.TransactionTest.java Source code

Java tutorial

Introduction

Here is the source code for de.hybris.platform.test.TransactionTest.java

Source

/*
 * [y] hybris Platform
 *
 * Copyright (c) 2000-2013 hybris AG
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of hybris
 * ("Confidential Information"). You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the
 * license agreement you entered into with hybris.
 * 
 *  
 */
package de.hybris.platform.test;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;

import de.hybris.bootstrap.annotations.IntegrationTest;
import de.hybris.platform.cache.Cache;
import de.hybris.platform.cache.InvalidationListener;
import de.hybris.platform.cache.InvalidationManager;
import de.hybris.platform.cache.InvalidationTarget;
import de.hybris.platform.cache.InvalidationTopic;
import de.hybris.platform.cache.RemoteInvalidationSource;
import de.hybris.platform.cluster.BroadcastService;
import de.hybris.platform.cluster.DefaultBroadcastService;
import de.hybris.platform.cluster.InvalidationBroadcastHandler;
import de.hybris.platform.core.Constants;
import de.hybris.platform.core.ItemDeployment;
import de.hybris.platform.core.PK;
import de.hybris.platform.core.Registry;
import de.hybris.platform.core.Tenant;
import de.hybris.platform.jalo.ConsistencyCheckException;
import de.hybris.platform.jalo.JaloBusinessException;
import de.hybris.platform.jalo.JaloItemNotFoundException;
import de.hybris.platform.jalo.JaloSession;
import de.hybris.platform.jalo.JaloSystemException;
import de.hybris.platform.jalo.c2l.C2LManager;
import de.hybris.platform.jalo.c2l.Country;
import de.hybris.platform.jalo.c2l.Currency;
import de.hybris.platform.jalo.media.MediaFormat;
import de.hybris.platform.jalo.media.MediaManager;
import de.hybris.platform.jalo.order.Order;
import de.hybris.platform.jalo.order.OrderManager;
import de.hybris.platform.jalo.product.Product;
import de.hybris.platform.jalo.product.ProductManager;
import de.hybris.platform.jalo.type.ComposedType;
import de.hybris.platform.jalo.user.Title;
import de.hybris.platform.jalo.user.UserManager;
import de.hybris.platform.jdbcwrapper.ConnectionImpl;
import de.hybris.platform.jdbcwrapper.HybrisDataSource;
import de.hybris.platform.jdbcwrapper.JUnitConnectionErrorCheckingJDBCConnectionPool;
import de.hybris.platform.jdbcwrapper.JUnitConnectionImpl;
import de.hybris.platform.jdbcwrapper.JUnitConnectionImpl.CommitMode;
import de.hybris.platform.persistence.framework.EntityInstance;
import de.hybris.platform.persistence.framework.EntityInstanceContext;
import de.hybris.platform.persistence.framework.PersistencePool;
import de.hybris.platform.test.TestThreadsHolder.RunnerCreator;
import de.hybris.platform.testframework.HybrisJUnit4Test;
import de.hybris.platform.testframework.TestUtils;
import de.hybris.platform.tx.DefaultTransaction;
import de.hybris.platform.tx.Transaction;
import de.hybris.platform.tx.TransactionBody;
import de.hybris.platform.tx.TransactionException;
import de.hybris.platform.util.Config;
import de.hybris.platform.util.StopWatch;
import de.hybris.platform.util.Utilities;
import de.hybris.platform.util.jeeapi.YObjectNotFoundException;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLTransactionRollbackException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Test;

@IntegrationTest
public class TransactionTest extends HybrisJUnit4Test {
    private static final Logger LOG = Logger.getLogger(TransactionTest.class);

    @After
    public void tearDown() {
        // make sure no (nested) transaction remains even if a
        // single test aborts too early due to failure
        while (Transaction.current().isRunning()) {
            try {
                LOG.warn("test " + getClass().getSimpleName() + " found open transaction - trying rollback ...");
                Transaction.current().rollback();
            } catch (final Exception e) {
                // can't help it
                break;
            }
        }
    }

    private boolean hasJUnitJDBCSetup() {
        return Registry.getCurrentTenantNoFallback().getDataSource()
                .getConnectionPool() instanceof JUnitConnectionErrorCheckingJDBCConnectionPool;
    }

    @Test
    public void testCommitWithActivateFalse() throws Exception {
        try {
            // simulate config setting with threadlocal flag -> has the same semantics!
            Transaction.enableUserTransactionForThread(false);

            final Title title = (Title) Transaction.current().execute(new TransactionBody() {
                @Override
                public Object execute() throws Exception {
                    return UserManager.getInstance().createTitle("TTT");
                }
            });

            assertNotNull(title);
            assertTrue(title.isAlive());
        } finally {
            Transaction.enableUserTransactionForThread(true);
        }
    }

    @Test
    public void testIneffectiveRollbackWithActivateFalse() throws Exception {
        try {
            final Transaction tx = Transaction.current();
            // simulate config setting with threadlocal flag -> has the same semantics!
            Transaction.enableUserTransactionForThread(false);

            final AtomicReference<PK> titlePKref = new AtomicReference<PK>();
            try {
                tx.execute(new TransactionBody() {
                    @Override
                    public Object execute() throws Exception {
                        titlePKref.set(UserManager.getInstance().createTitle("TTT").getPK());
                        throw new RuntimeException("rollback please");
                    }
                });
                fail("RuntimeException expected");
            } catch (final RuntimeException e) {
                assertEquals("rollback please", e.getMessage());
            }

            assertNotSame(tx, Transaction.current());
            assertNotNull(titlePKref.get());
            final Title title = jaloSession.getItem(titlePKref.get());
            assertNotNull(title);
            assertTrue(title.isAlive());
        } finally {
            Transaction.enableUserTransactionForThread(true);
        }
    }

    @Test
    public void testErrorOnBeginWithNullConnection() throws Exception {
        final DefaultTransaction transaction = new DefaultTransaction() {
            @Override
            protected ConnectionImpl getConnectionToBind(final HybrisDataSource dataSource) throws SQLException {
                return null;
            }
        };
        transaction.activateAsCurrentTransaction();
        assertSame(transaction, Transaction.current());

        try {
            transaction.begin();
            fail("Expected exception on Transaction.begin() with NULL connection !!!");
        } catch (final Exception e) {
            // fair enough
        }

        assertFalse(transaction.isRunning());
        assertNotSame(transaction, Transaction.current());
    }

    @Test
    public void testErrorOnBeginWithExceptionInDataSource() throws Exception {
        final DefaultTransaction transaction = new DefaultTransaction() {
            @Override
            protected ConnectionImpl getConnectionToBind(final HybrisDataSource dataSource) throws SQLException {
                throw new SQLException("weird test exception");
            }
        };
        transaction.activateAsCurrentTransaction();
        assertSame(transaction, Transaction.current());

        try {
            transaction.begin();
            fail("Expected exception on Transaction.begin() with NULL connection !!!");
        } catch (final Exception e) {
            // fair enough
        }

        assertFalse(transaction.isRunning());
        assertNotSame(transaction, Transaction.current());
    }

    @Test
    public void testItemAccessDuringCommit_PLA10839() throws Exception {
        final Title title1 = UserManager.getInstance().createTitle("t1");
        final Title title2 = UserManager.getInstance().createTitle("t2");
        final Title title3 = UserManager.getInstance().createTitle("t3");
        final Title title4 = UserManager.getInstance().createTitle("t4");

        final AtomicBoolean listenerHasBeenCalled = new AtomicBoolean(false);

        final InvalidationListener listener = new InvalidationListener() {
            @Override
            public void keyInvalidated(final Object[] key, final int invalidationType,
                    final InvalidationTarget target, final RemoteInvalidationSource remoteSrc) {
                listenerHasBeenCalled.set(true);
                // access t1 here
                if (StringUtils.isEmpty(title1.getName())) {
                    System.err.println("title1 name is empty");
                }
            }
        };
        final InvalidationTopic topic = InvalidationManager.getInstance()
                .getInvalidationTopic(new Object[] { Cache.CACHEKEY_HJMP, Cache.CACHEKEY_ENTITY });
        try {
            topic.addInvalidationListener(listener);

            final Transaction tx = Transaction.current();
            tx.execute(new TransactionBody() {

                @Override
                public Object execute() throws Exception {
                    title2.setName("foo");
                    title3.setName("foo");
                    title4.setName("foo");
                    return null;
                }
            });
            assertEquals("foo", title2.getName());
            assertEquals("foo", title3.getName());
            assertEquals("foo", title4.getName());
            assertTrue(listenerHasBeenCalled.get());
        } finally {
            topic.removeInvalidationListener(listener);
        }
    }

    @Test
    public void testItemUpdateDuringCommit_PLA10839() throws Exception {
        final Title title1 = UserManager.getInstance().createTitle("t1");
        final Title title2 = UserManager.getInstance().createTitle("t2");
        final Title title3 = UserManager.getInstance().createTitle("t3");
        final Title title4 = UserManager.getInstance().createTitle("t4");

        final AtomicBoolean listenerHasBeenCalled = new AtomicBoolean(false);

        final InvalidationListener listener = new InvalidationListener() {
            @Override
            public void keyInvalidated(final Object[] key, final int invalidationType,
                    final InvalidationTarget target, final RemoteInvalidationSource remoteSrc) {
                listenerHasBeenCalled.set(true);
                // change t1 here
                title1.setName("newOne");
            }
        };
        final InvalidationTopic topic = InvalidationManager.getInstance()
                .getInvalidationTopic(new Object[] { Cache.CACHEKEY_HJMP, Cache.CACHEKEY_ENTITY });
        try {
            topic.addInvalidationListener(listener);

            final Transaction tx = Transaction.current();
            tx.execute(new TransactionBody() {

                @Override
                public Object execute() throws Exception {
                    title2.setName("foo");
                    title3.setName("foo");
                    title4.setName("foo");
                    return null;
                }
            });
            assertEquals("foo", title2.getName());
            assertEquals("foo", title3.getName());
            assertEquals("foo", title4.getName());
            assertEquals("newOne", title1.getName());
            assertTrue(listenerHasBeenCalled.get());
        } finally {
            topic.removeInvalidationListener(listener);
        }
    }

    @Test
    public void testErrorInNestedCommit() {
        final Transaction tx = new DefaultTransaction() {
            @Override
            public void flushDelayedStore() {
                throw new TestTxException();
            }
        };
        tx.activateAsCurrentTransaction();
        assertSame("Wrong current transaction", tx, Transaction.current());
        try {
            // 1. outer begin must be fine
            tx.begin();

            assertSame("Wrong current transaction", tx, Transaction.current());
            assertEquals("Open connection count is not correct", 1, tx.getOpenTransactionCount());

            // 2. nested begin calls flushDelayedStore() -> error
            try {
                tx.begin();
                fail("TransactionException expected");
            } catch (final TestTxException e) {
                // fine
            } catch (final Exception e) {
                fail("unexpected transaction exception " + e);
            }
            assertSame("Wrong current transaction", tx, Transaction.current());
            assertEquals("Open connection count is wrongly increased", 1, tx.getOpenTransactionCount());
            assertTrue("Transaction is not marked as rollback-only", tx.isRollbackOnly());
            assertTrue("Transaction is no longer running", tx.isRunning());

            // 3. rollback() -> must be fine again
            tx.rollback();

            assertFalse(tx.isRunning());
            assertNotSame(tx, Transaction.current());
        } finally {
            cleanup(tx, null);
        }
    }

    private static class TestTxException extends RuntimeException {
        //
    }

    @Test
    public void testErrorInNestedCommitUsingTxBody() {
        testErrorInNestedCommitUsingTxBody(true);
        testErrorInNestedCommitUsingTxBody(false);
    }

    private void testErrorInNestedCommitUsingTxBody(final boolean throwErrorInBegin) {
        final Transaction tx = new DefaultTransaction() {
            private boolean inBegin = false;
            private boolean inCommit = false;

            @Override
            public void begin() throws TransactionException {
                inBegin = true;
                try {
                    super.begin();
                } finally {
                    inBegin = false;
                }
            }

            @Override
            public void commit() throws TransactionException {
                inCommit = true;
                try {
                    super.commit();
                } finally {
                    inCommit = false;
                }
            }

            @Override
            public void flushDelayedStore() {
                if ((throwErrorInBegin && inBegin) || (!throwErrorInBegin && inCommit)) {
                    throw new TestTxException();
                }
            }
        };
        tx.activateAsCurrentTransaction();
        assertSame("Wrong current transaction", tx, Transaction.current());
        try {
            // 1. outer begin must be fine
            try {
                tx.execute(new TransactionBody() {
                    @Override
                    public Object execute() throws Exception {
                        assertSame("Wrong current transaction", tx, Transaction.current());
                        assertEquals("Open connection count is not correct", 1, tx.getOpenTransactionCount());

                        // 2. nested begin calls flushDelayedStore() -> error
                        try {
                            tx.execute(new TransactionBody() {
                                @Override
                                public Object execute() throws Exception {
                                    if (throwErrorInBegin) {
                                        fail("TransactionException expected");
                                    }
                                    return null;
                                }
                            });
                        } catch (final TestTxException e) {
                            // check that we got the correct error
                            assertSame("Wrong current transaction after begin() error", tx, Transaction.current());
                            assertEquals("Open connection count is not correct after inner begin() error", 1,
                                    tx.getOpenTransactionCount());
                            // pass on to outer tx
                            throw e;
                        } catch (final Exception e) {
                            fail("unexpected inner transaction exception " + e);
                        }
                        return null;
                    }
                });
            } catch (final TestTxException e) {
                // fine
            } catch (final Exception e) {
                fail("unexpected outer transaction exception " + e);
            }

            assertFalse(tx.isRunning());
            assertNotSame(tx, Transaction.current());
        } finally {
            cleanup(tx, null);
        }
    }

    @Test
    public void testRollbackOnCommitError() throws SQLException {
        if (hasJUnitJDBCSetup()) {
            final Transaction tx = Transaction.current();
            JUnitConnectionImpl con = null;
            try {
                tx.begin();
                tx.setRollbackOnCommitError(true);
                con = ((JUnitConnectionImpl) tx.getTXBoundConnection());

                // simulate error which leaves transaction open !
                con.setCommitMode(CommitMode.NO_COMMIT_ERROR);

                assertFalse("autocommit is still on after begin", con.getAutoCommit());
                try {
                    tx.commit();
                } catch (final TransactionException e) {
                    assertTrue("unexpected commit exception " + e.getCause(),
                            e.getCause() instanceof SQLTransactionRollbackException);
                }
                assertFalse("transaction is still running after rollback-on-commit-error", tx.isRunning());
                assertTrue("autocommit is still off after commit/rollback", con.getAutoCommit());
                assertFalse("rollbacked (real) connection is closed", con.getUnderlayingConnection().isClosed());
            } finally {
                cleanup(tx, con);
            }
        } else {
            LOG.warn(
                    "Cannot run TransactionTest.testRollbackOnCommitError() since current tennat doesnt have JUnit JDBC setup!");
        }
    }

    @Test
    public void testConnectionNoClosedAfterRollback() throws SQLException {
        if (hasJUnitJDBCSetup()) {
            final Transaction tx = Transaction.current();
            JUnitConnectionImpl con = null;
            try {
                tx.begin();
                con = ((JUnitConnectionImpl) tx.getTXBoundConnection());

                assertFalse("autocommit is still on", con.getAutoCommit());

                tx.rollback();

                assertFalse("rollbacked (real) connection is closed", con.getUnderlayingConnection().isClosed());
                assertTrue("autocommit is still off", con.getAutoCommit());
            } finally {
                cleanup(tx, con);
            }
        } else {
            LOG.warn(
                    "Cannot run TransactionTest.testConnectionNoClosedAfterRollback() since current tennat doesnt have JUnit JDBC setup!");
        }
    }

    @Test
    public void testErrorInCommit() throws SQLException {
        if (hasJUnitJDBCSetup()) {
            final Transaction tx = Transaction.current();

            JUnitConnectionImpl con = null;
            try {
                tx.begin();
                con = ((JUnitConnectionImpl) tx.getTXBoundConnection());
                // simulate error in commit ( tx will be committed though )
                con.setCommitMode(CommitMode.COMMIT_AND_ERROR);

                assertFalse("autocommit is still on", con.getAutoCommit());

                try {
                    tx.commit();
                    fail("TransactionException expected");
                } catch (final TransactionException e) {
                    assertTrue("unexpected commit exception " + e.getCause(),
                            e.getCause() instanceof SQLTransactionRollbackException);
                }
                assertTrue("autocommit is still off", con.getAutoCommit());
            } finally {
                cleanup(tx, con);
            }
        } else {
            LOG.warn(
                    "Cannot run TransactionTest.testErrorInCommit() since current tennat doesnt have JUnit JDBC setup!");
        }
    }

    private void cleanup(final Transaction tx, final JUnitConnectionImpl con) {
        if (con != null) {
            con.resetTestMode();
        }
        try {
            if (tx.isRunning()) {
                tx.rollback();
            }
        } catch (final Exception e) {
            LOG.error("error cleaning up transaction", e);
        }
    }

    @Test
    public void testLocking() throws Exception {

        if (Config.isHSQLDBUsed()) {
            LOG.warn("HDSQLDB doesnt seem to support SELECT FOR UPDATE properly so we don't test it any more");
            return;
        }

        final ProductManager productManager = ProductManager.getInstance();

        final Currency curr = C2LManager.getInstance().createCurrency("TestCurr");

        /** Verify that we can begin a transaction, lock an entity, then commit without an exception occurring. */
        {
            final Transaction transaction = Transaction.current();
            try {
                assertNotNull("Transaction object is null", transaction);
                assertFalse("A previous transaction is already running.", transaction.isRunning());
                transaction.begin();
                final Product productForTest1 = productManager.createProduct("transactionLockingTest1");
                transaction.commit();
                transaction.begin();
                transaction.lock(productForTest1);
                transaction.commit();
            } catch (final Exception e) {
                transaction.rollback();
                throw e;
            }
        }

        {
            /** Verify that an IllegalStateException is thrown if we attempt to lock outside of a transaction. */
            final Transaction transaction = Transaction.current();
            try {
                assertNotNull("Transaction object is null", transaction);
                assertFalse("A previous transaction is already running.", transaction.isRunning());
                final Product productForTest2 = productManager.createProduct("transactionLockingTest2");
                transaction.lock(productForTest2);
                fail("Expected IllegalStateException to occur when attempting to lock an item outside of a transaction.");
            }
            // An IllegalStateException is expected for this test to pass.
            catch (final IllegalStateException e) {
                //
            }
        }

        /**
         * Verify that if we attempt to acquire a lock on the same entity multiple times from the same transaction, that
         * no errors occur.
         */
        {
            final Transaction transaction = Transaction.current();
            try {
                assertNotNull("Transaction object is null", transaction);
                assertFalse("A previous transaction is already running.", transaction.isRunning());
                final Product productForTest3 = productManager.createProduct("transactionLockingTest3");
                transaction.begin();
                for (int i = 0; i < 10; i++) {
                    transaction.lock(productForTest3);
                }
                transaction.commit();
            } catch (final Exception e) {
                transaction.rollback();
                throw e;
            }
        }

        /**
         * Verify that if we begin a transaction, lock an entity, then commit multiple times that a lock can be acquired
         * each time.
         */
        {
            final Transaction transaction = Transaction.current();
            try {
                final Product productForTest4 = productManager.createProduct("transactionLockingTest4");
                for (int i = 0; i < 10; i++) {
                    assertNotNull("Transaction object is null", transaction);
                    assertFalse("A previous transaction is already running.", transaction.isRunning());
                    transaction.begin();
                    transaction.lock(productForTest4);
                    transaction.commit();
                }
            } catch (final Exception e) {
                transaction.rollback();
                throw e;
            }
        }

        /**
         * Verify that if we begin a transaction, lock an entity, then rollback multiple times that a lock can be acquired
         * each time.
         */
        {
            final Transaction transaction = Transaction.current();
            try {
                final Product productForTest5 = productManager.createProduct("transactionLockingTest5");
                for (int i = 0; i < 10; i++) {
                    assertNotNull("Transaction object is null", transaction);
                    assertFalse("A previous transaction is already running.", transaction.isRunning());
                    transaction.begin();
                    transaction.lock(productForTest5);
                    transaction.rollback();
                }
            } catch (final Exception e) {
                transaction.rollback();
                throw e;
            }
        }

        /**
         * Verify that we can not lock after a transaction has been committed.
         */
        {
            final Transaction transaction = Transaction.current();
            try {
                final Product productForTest6 = productManager.createProduct("transactionLockingTest6");
                assertNotNull("Transaction object is null", transaction);
                assertFalse("A previous transaction is already running.", transaction.isRunning());
                transaction.begin();
                transaction.commit();
                transaction.lock(productForTest6);
                fail("A lock was acquired after the transaction has been committed.");
            }
            // An IllegalStateException is expected for the test to pass
            catch (final IllegalStateException e) {
                //
            }
        }

        /**
         * Verify that we can not lock after a transaction has been rolled back.
         */
        {
            final Transaction transaction = Transaction.current();
            try {
                final Product productForTest7 = productManager.createProduct("transactionLockingTest7");
                assertNotNull("Transaction object is null", transaction);
                assertFalse("A previous transaction is already running.", transaction.isRunning());
                transaction.begin();
                transaction.rollback();
                transaction.lock(productForTest7);
                fail("A lock was acquired after the transaction has been rolled back.");
            }
            // An IllegalStateException is expected for the test to pass
            catch (final IllegalStateException e) {
                //
            }
        }

        /**
         * Verify multiple threads attempting to lock the same object and the behavior that occurs.
         */
        try {
            final Order lockedOrder = OrderManager.getInstance().createOrder(//
                    "lockedOrder", //
                    JaloSession.getCurrentSession().getUser(), //
                    curr, //
                    Calendar.getInstance().getTime(), //
                    true);
            lockedOrder.setTotal(0.0d);

            final ComposedType composedType = lockedOrder.getComposedType();

            final String checkQuery = "SELECT "
                    + composedType.getAttributeDescriptorIncludingPrivate(Order.TOTAL).getDatabaseColumn()
                    + " FROM " + composedType.getTable() + " WHERE PK = ?";

            final int THREADS = 16;

            // Create an executor service that uses 16 threads to test
            // the transaction locking
            final ExecutorService executor = Executors.newFixedThreadPool(//
                    THREADS, //
                    new ThreadFactory() {
                        final Tenant threadFactoryTenant = Registry.getCurrentTenant();

                        @Override
                        public Thread newThread(final Runnable runnable) {
                            return new Thread() {
                                protected void prepareThread() {
                                    Registry.setCurrentTenant(threadFactoryTenant);
                                }

                                protected void unprepareThread() {
                                    JaloSession.deactivate();
                                    Registry.unsetCurrentTenant();
                                }

                                @Override
                                public void run() {
                                    try {
                                        prepareThread();
                                        runnable.run();
                                    } finally {
                                        unprepareThread();
                                    }
                                }
                            };
                        }
                    });

            // Create 8 callables that will concurrently
            // attempt to lock the same object.
            final AtomicInteger stackCounter = new AtomicInteger();
            final List<Callable<Object>> callables = new ArrayList<Callable<Object>>();
            for (int j = 0; j < THREADS; j++) {
                callables.add(new Callable<Object>() {
                    @Override
                    public Object call() throws Exception {
                        final PK pk = lockedOrder.getPK();
                        if (pk == null) {
                            throw new IllegalStateException();
                        }

                        for (int k = 0; k < 100; k++) {
                            final Transaction transaction = Transaction.current();

                            assertNotNull("Transaction object is null", transaction);

                            PreparedStatement statement = null;
                            ResultSet resultSet = null;
                            try {
                                transaction.begin();
                                transaction.setTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED);
                                transaction.lock(lockedOrder);
                                final int stack = stackCounter.incrementAndGet();
                                if (stack > 1) {
                                    stackCounter.decrementAndGet();
                                    throw new IllegalStateException("Got " + stack + " threads in protected area!");
                                }

                                statement = transaction.getTXBoundConnection().prepareStatement(checkQuery);
                                statement.setLong(1, lockedOrder.getPK().getLongValue());
                                resultSet = statement.executeQuery();
                                if (!resultSet.next()) {
                                    throw new IllegalStateException("Expected result set");
                                }
                                final double dbValue = resultSet.getDouble(1);
                                final double jaloValue = lockedOrder.getTotal();
                                if (Math.abs(dbValue - jaloValue) >= 1d) {
                                    throw new IllegalStateException(
                                            "Jalo value differs from db value : " + jaloValue + "<>" + dbValue);
                                }

                                lockedOrder.setTotal(jaloValue + 1.0d);

                                stackCounter.decrementAndGet();
                                transaction.commit();
                            } catch (final Exception e) {
                                e.printStackTrace();
                                transaction.rollback();
                                throw e;
                            } finally {
                                Utilities.tryToCloseJDBC(null, statement, resultSet, true);
                            }
                        }
                        return null;
                    }
                });
            }
            // Get the value of each future to determine if an exception was thrown.
            for (final Future<Object> future : executor.invokeAll(callables)) {
                future.get();
            }
            final double expected = THREADS * 100;
            assertEquals(//
                    "Total value of order after all transaction differs", //
                    expected, //
                    ((Order) JaloSession.getCurrentSession().getItem(lockedOrder.getPK())).getTotal(), 0.000001);
        } catch (final IllegalStateException e) {
            e.printStackTrace();
            throw e;
        }

        /**
         * Verify changes to a value on a lock
         */

        // TODO:

        /**
         * Tests related to caching
         */

        // TODO:
    }

    @Test
    public void testGetAllAttributes() throws Exception {
        final Product product = ProductManager.getInstance().createProduct("acode");

        final int CNT = 10000;

        StopWatch stopWatch = new StopWatch("getAllAttributes");
        for (int i = 0; i < CNT; i++) {
            product.getAllAttributes();
        }
        stopWatch.stop();

        stopWatch = new StopWatch("getAllProperties");
        for (int i = 0; i < CNT; i++) {
            product.getAllProperties();
        }
        stopWatch.stop();
    }

    @Test
    public void testInTXModification1() throws Exception {
        final Country country = C2LManager.getInstance().createCountry("before");
        assertNotNull(country);
        Transaction.current().execute(new TransactionBody() {
            @Override
            public Object execute() throws Exception {
                Transaction.current().enableDelayedStore(false);
                try {
                    country.setIsoCode("after");
                    C2LManager.getInstance().getCountryByIsoCode("before"); //should find the old
                    fail("Jaloitemnotfound should occur!");
                } catch (final JaloItemNotFoundException e) {
                    // DOCTODO Document reason, why this block is empty
                }
                try {
                    C2LManager.getInstance().getCountryByIsoCode("after"); //and not! the new
                } catch (final JaloItemNotFoundException e) {
                    fail("Jaloitemnotfound should not occur!");
                }
                return null;
            }
        });
    }

    @Test
    public void testInTXModification2() throws Exception {
        final Country country = C2LManager.getInstance().createCountry("before");
        assertNotNull(country);
        Transaction.current().execute(new TransactionBody() {
            @Override
            public Object execute() throws Exception {
                Transaction.current().enableDelayedStore(true);
                try {
                    country.setIsoCode("after");
                    C2LManager.getInstance().getCountryByIsoCode("after"); //should find the old
                    fail("JaloItemNotFound should occur!");
                } catch (final JaloItemNotFoundException e) {
                    // DOCTODO Document reason, why this block is empty
                }
                try {
                    C2LManager.getInstance().getCountryByIsoCode("before"); //and not! the new
                } catch (final JaloItemNotFoundException e) {
                    fail("Jaloitemnotfound should not occur!");
                }
                Transaction.current().flushDelayedStore();
                try {
                    C2LManager.getInstance().getCountryByIsoCode("after"); //should find the new
                } catch (final JaloItemNotFoundException e) {
                    fail("Jaloitemnotfound should not occur!");
                }
                return null;
            }
        });
    }

    @Test
    public void testInTXRemoval() throws Exception {

        final String iso = PK.createUUIDPK(0).getHex();
        final Country country = C2LManager.getInstance().createCountry(iso);

        Transaction.current().execute(new TransactionBody() {
            @Override
            public Object execute() throws Exception {
                Transaction.current().enableDelayedStore(false);
                country.remove();
                try {
                    C2LManager.getInstance().getCountryByIsoCode(iso);
                    fail("Jaloitemnotfound should occur!");
                } catch (final JaloItemNotFoundException e) {
                    //ok, good
                }
                return null;
            }
        });

    }

    @Test
    public void testGlobalCacheInvalidationMessageSent() throws Exception {
        final int MAX_WAIT_SECONDS = 15;

        assertFalse(Transaction.current().isRunning());
        final Country country = jaloSession.getC2LManager().createCountry("Country");
        final PK pk = country.getPK();

        final DefaultBroadcastService broadcastService = DefaultBroadcastService.getInstance();
        final TestInvalidationMessageHandler messageHandler = new TestInvalidationMessageHandler(broadcastService);
        try {
            broadcastService.registerBroadcastListener(messageHandler, false);

            Transaction.current().execute(new TransactionBody() {
                @Override
                public Object execute() throws Exception {
                    assertEquals("Country", country.getIsoCode());
                    assertFalse("got message before update in tx", messageHandler.gotEventFor(pk));
                    country.setIsocode("CountryNew");
                    assertFalse("got message after update but still in tx", messageHandler.gotEventFor(pk));
                    return null;
                }
            });

            assertEquals("CountryNew", country.getIsoCode());
            final long maxWait = System.currentTimeMillis() + (MAX_WAIT_SECONDS * 1000);
            boolean gotEvent = false;
            do {
                Thread.sleep(100);
                gotEvent = messageHandler.gotEventFor(pk);
            } while (!gotEvent && System.currentTimeMillis() < maxWait);
            assertTrue(gotEvent);
        } finally {
            broadcastService.unregisterBroadcastListener(messageHandler);
        }
    }

    static class TestInvalidationMessageHandler extends InvalidationBroadcastHandler {
        public TestInvalidationMessageHandler(final BroadcastService broadcastService) {
            super(broadcastService);
        }

        final List<InvalidationEvent> events = new CopyOnWriteArrayList<InvalidationBroadcastHandler.InvalidationEvent>();

        @Override
        protected void processInvalidation(final InvalidationEvent event,
                final de.hybris.platform.cluster.RawMessage message) {
            events.add(event);
        }

        boolean gotEventFor(final PK itemPK) {
            for (final InvalidationEvent e : events) {
                final Object[] key = e.getKey();
                if (key.length == 4 && Cache.CACHEKEY_HJMP.equals(key[0]) && Cache.CACHEKEY_ENTITY.equals(key[1])
                        && Integer.toString(itemPK.getTypeCode()).equals(key[2]) && itemPK.equals(key[3])) {
                    return true;
                }
            }
            return false;
        }
    }

    @Test
    public void testJaloCacheInvalidation() throws Exception {
        Country country = null;
        assertFalse(Transaction.current().isRunning());
        assertNotNull(country = jaloSession.getC2LManager().createCountry("code11"));
        assertEquals("code11", country.getIsoCode());

        Transaction.current().begin();
        assertEquals("code11", country.getIsoCode());
        country.setIsoCode("code2");
        assertEquals("code2", country.getIsoCode());
        LOG.debug("before commit");
        Transaction.current().commit();
        LOG.debug("after commit");
        assertEquals("code2", country.getIsoCode());

        Transaction.current().begin();
        LOG.debug("RRgetting iso!");
        assertEquals("code2", country.getIsoCode());
        LOG.debug("RRsetting iso!");
        country.setIsoCode("code3");
        LOG.debug("RRgetting iso!");
        assertEquals("code3", country.getIsoCode());
        LOG.debug("before: " + country.getIsoCode());
        Transaction.current().rollback();

        LOG.debug("after: " + country.getIsoCode());
        assertEquals("code2", country.getIsoCode());
    }

    public void testCacheInvalidationWithUserTransaction() throws Exception {
        Country country = null;
        assertNotNull(country = jaloSession.getC2LManager().createCountry("code1"));
        assertEquals("code1", country.getIsoCode());

        boolean done = false;
        try {
            Transaction.current().begin();
            assertEquals("code1", country.getIsoCode());
            country.setIsoCode("code2");
            assertEquals("code2", country.getIsoCode());
            Transaction.current().commit();
            done = true;
            assertEquals("code2", country.getIsoCode());
        } finally {
            if (!done) {
                Transaction.current().rollback();
            }
        }
        done = false;
        try {
            Transaction.current().begin();
            assertEquals("code2", country.getIsoCode());
            country.setIsoCode("code3");
            LOG.debug("setting code");
            assertEquals("code3", country.getIsoCode());
            LOG.debug("rolling back");
            Transaction.current().rollback();
            done = true;
            LOG.debug("getting code");
            assertEquals("code2", country.getIsoCode());
        } finally {
            if (!done) {
                Transaction.current().rollback();
            }
        }
    }

    @Test
    public void testIsolation() throws ConsistencyCheckException {
        if (Config.itemCacheIsolationActivated()) {
            testIsolationInternal(false);
            testIsolationInternal(true);
        } else {
            LOG.warn("jalo item isolation is currently disabled - skipped test");
        }
    }

    private void testIsolationInternal(final boolean rollbackChanges) throws ConsistencyCheckException {
        final Country country = jaloSession.getC2LManager().createCountry("code1");
        try {
            assertEquals("code1", country.getIsoCode());

            final RunnerCreator<IsolationTestRunnable> creator = new RunnerCreator<TransactionTest.IsolationTestRunnable>() {
                final CyclicBarrier txSync = new CyclicBarrier(2);
                final CountDownLatch waitForWrite = new CountDownLatch(1);
                final CountDownLatch codeWritten = new CountDownLatch(1);

                @Override
                public IsolationTestRunnable newRunner(final int threadNumber) {
                    return new IsolationTestRunnable(country, threadNumber == 0, rollbackChanges, waitForWrite,
                            codeWritten, txSync);
                }
            };

            final TestThreadsHolder<IsolationTestRunnable> threads = new TestThreadsHolder<TransactionTest.IsolationTestRunnable>(
                    2, creator);

            threads.startAll();

            assertTrue("not all threads have finished properly", threads.waitAndDestroy(30));

            assertEquals(Collections.EMPTY_MAP, threads.getErrors());
        } finally {
            if (country != null && country.isAlive()) {
                try {
                    country.remove();
                } catch (final Exception e) {
                    // for several reasons this may fail without meaning harm so we ignore it here
                }
            }
        }

    }

    private static class IsolationTestRunnable implements Runnable {
        final Tenant tenant;
        final JaloSession jaloSession;
        final Country country;
        final boolean first;
        final boolean rollbackChanges;
        final CountDownLatch waitForWrite, codeWritten;
        final CyclicBarrier txSync;

        IsolationTestRunnable(final Country country, final boolean first, final boolean rollbackChanges,
                final CountDownLatch waitForWrite, final CountDownLatch codeWritten, final CyclicBarrier txSync) {
            this.country = country;
            this.first = first;
            this.rollbackChanges = rollbackChanges;
            this.waitForWrite = waitForWrite;
            this.codeWritten = codeWritten;
            this.txSync = txSync;

            this.tenant = Registry.getCurrentTenantNoFallback();
            this.jaloSession = JaloSession.getCurrentSession();
        }

        @Override
        public void run() {
            try {
                Registry.setCurrentTenant(tenant);
                jaloSession.activate();
                if (first) {
                    runFirst();
                } else {
                    runSecond();
                }
            } catch (final Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } finally {
                JaloSession.deactivate();
                Registry.unsetCurrentTenant();
            }
        }

        private void runFirst() throws Exception {
            assertEquals("code1", country.getIsoCode());

            final Transaction tx = Transaction.current();
            tx.execute(new TransactionBody() {
                @Override
                public Object execute() throws Exception {
                    txSync.await(10, TimeUnit.SECONDS); // let both threads join here

                    assertEquals("code1", country.getIsoCode()); // first read inside transaction

                    waitForWrite.countDown(); // let thread 2 change the code in his transaction
                    assertTrue(codeWritten.await(10, TimeUnit.SECONDS)); // wait for other thread to actually perform writing

                    assertEquals("code1", country.getIsoCode());

                    txSync.await(10, TimeUnit.SECONDS); // let both threads join here

                    return null;
                }
            });
        }

        private void runSecond() throws Exception {
            assertEquals("code1", country.getIsoCode());

            final Transaction tx = Transaction.current();
            try {
                tx.execute(new TransactionBody() {
                    @Override
                    public Object execute() throws Exception {
                        txSync.await(10, TimeUnit.SECONDS); // let both threads join here

                        assertEquals("code1", country.getIsoCode());

                        assertTrue(waitForWrite.await(10, TimeUnit.SECONDS)); // wait for other thread to have read old value

                        country.setIsoCode("t2Code"); // set new value

                        assertEquals("t2Code", country.getIsoCode());

                        codeWritten.countDown(); // signal that code has been written

                        assertEquals("t2Code", country.getIsoCode());

                        txSync.await(10, TimeUnit.SECONDS); // let both threads join here

                        if (rollbackChanges) {
                            tx.setRollbackOnly();
                        }

                        return null;
                    }
                });
            } catch (final TransactionException e) {
                // this is allowed in case we are supposed to rollback
                assertTrue("unexpected tx exception " + e, rollbackChanges);
            }
            if (rollbackChanges) {
                assertEquals("code1", country.getIsoCode());
            } else {
                assertEquals("t2Code", country.getIsoCode());
            }
        }
    }

    @Test
    public void testDeadlocks() throws Exception {
        Product product = null;

        try {
            Transaction.current().begin();

            assertNotNull(product = ProductManager.getInstance().createProduct("txtest"));
            final ComposedType productType1 = jaloSession.getTypeManager()
                    .getRootComposedType(Constants.TC.Product);
            final ComposedType productType2 = product.getComposedType();

            assertEquals(productType1.getCode(), productType2.getCode());

            Transaction.current().commit();
        } catch (final Exception e) {
            Transaction.current().rollback();
            if (product != null) {
                assertNotNull(product);
            }
            throw e;
        }
    }

    @Test
    public void testNestedTransactionCommit() throws ConsistencyCheckException {
        assertEquals(0, Transaction.current().getOpenTransactionCount());
        assertFalse(Transaction.current().isRunning());
        Transaction.current().begin(); // 1 x begin -> 1 open
        assertTrue(Transaction.current().isRunning());
        assertEquals(1, Transaction.current().getOpenTransactionCount());

        final Country country = C2LManager.getInstance().createCountry("Thalerland");
        assertNotNull(country);
        assertNotNull(country);
        assertEquals(country, C2LManager.getInstance().getCountryByIsoCode("Thalerland"));

        for (int i = 0; i < 1000; i++) // 1000 x begin -> 1001 open
        {
            Transaction.current().begin();
        }

        assertEquals(1001, Transaction.current().getOpenTransactionCount());
        assertTrue(Transaction.current().isRunning());
        assertEquals(country, C2LManager.getInstance().getCountryByIsoCode("Thalerland"));

        for (int i = 0; i < 999; i++) // 999 x commit -> 2 open
        {
            Transaction.current().commit();
        }

        assertEquals(2, Transaction.current().getOpenTransactionCount());
        assertTrue(Transaction.current().isRunning());
        assertEquals(country, C2LManager.getInstance().getCountryByIsoCode("Thalerland"));

        Transaction.current().rollback(); // rollback on 2nd level -> mark rollback only, 1 open now

        assertEquals(1, Transaction.current().getOpenTransactionCount());
        assertTrue(Transaction.current().isRunning());
        assertTrue(Transaction.current().isRollbackOnly());
        assertEquals(country, C2LManager.getInstance().getCountryByIsoCode("Thalerland"));
        try {
            Transaction.current().commit(); // commit on first level -> error
            fail("exepcted TransactionException");
        } catch (final TransactionException e) {
            // fine - transaction has been rolled back
            assertTrue(e.getMessage().contains("rollback-only"));
            assertFalse(Transaction.current().isRollbackOnly());
            assertFalse(Transaction.current().isRunning());
        }

        assertFalse(Transaction.current().isRunning());
        assertEquals(0, Transaction.current().getOpenTransactionCount());

        try {
            C2LManager.getInstance().getCountryByIsoCode("Thalerland");
            fail("JaloItemNotFoundException exepcted");
        } catch (final JaloItemNotFoundException e) {
            // fine
        }
    }

    @Test
    public void testNestedTransactionRollback() throws ConsistencyCheckException {
        assertEquals(0, Transaction.current().getOpenTransactionCount());
        assertFalse(Transaction.current().isRunning());
        Transaction.current().begin();
        assertTrue(Transaction.current().isRunning());
        assertEquals(1, Transaction.current().getOpenTransactionCount());

        final Country country = C2LManager.getInstance().createCountry("Thalerland");
        assertNotNull(country);
        assertEquals(country, C2LManager.getInstance().getCountryByIsoCode("Thalerland"));

        Transaction.current().begin();

        assertTrue(Transaction.current().isRunning());
        assertEquals(2, Transaction.current().getOpenTransactionCount());

        Transaction.current().commit();

        assertEquals(1, Transaction.current().getOpenTransactionCount());
        assertTrue(Transaction.current().isRunning());
        assertEquals(country, C2LManager.getInstance().getCountryByIsoCode("Thalerland"));

        assertNotNull(country);

        Transaction.current().rollback();

        try {
            C2LManager.getInstance().getCountryByIsoCode("Thalerland");
            fail("JaloItemNotFoundException expected");
        } catch (final JaloItemNotFoundException e) {
            // OK
        }
        assertEquals(0, Transaction.current().getOpenTransactionCount());
        assertFalse(Transaction.current().isRunning());
    }

    @Test
    public void testNestedTransaction() throws ConsistencyCheckException {
        final Transaction tx = Transaction.current();
        assertTxState(tx, true, 0, false, false);

        // BEGIN -> 1
        tx.begin();
        assertTxState(tx, true, 1, true, false);

        //    BEGIN -> 2
        tx.begin();
        assertTxState(tx, true, 2, true, false);

        //    1 <- ROLLBACK + rollback-only
        tx.rollback();
        assertTxState(tx, true, 1, true, true);

        //    BEGIN -> 2 + rollback-only
        tx.begin();
        assertTxState(tx, true, 2, true, true);

        //    1 <- COMMIT + rollback-only
        tx.commit();
        assertTxState(tx, true, 1, true, true);

        // 0 <- ROLLBACK => no longer current !
        tx.rollback();
        assertTxState(tx, false, 0, false, false);

        final Transaction txNew = Transaction.current();
        assertNotSame(tx, txNew);

        try {
            txNew.commit();
            fail("TransactionException expected, because there was a commit without preceeding begin");
        } catch (final TransactionException e) {
            // OK
        }

        final Transaction txSame = Transaction.current();
        assertSame(txNew, txSame);

        try {
            txSame.rollback();
            fail("TransactionException expected, because there was a rollback without preceeding begin");
        } catch (final TransactionException e) {
            // OK
        }
    }

    private void assertTxState(final Transaction tx, final boolean isCurrent, final int openTxCount,
            final boolean running, final boolean rollbackOnly) {
        assertEquals(isCurrent, tx.isCurrent());
        assertEquals(openTxCount, tx.getOpenTransactionCount());
        assertEquals(running, tx.isRunning());
        assertEquals(rollbackOnly, tx.isRollbackOnly());
    }

    @Test
    public void testNestedTransactionExecute() throws Exception {
        assertEquals(0, Transaction.current().getOpenTransactionCount());
        assertFalse(Transaction.current().isRunning());

        Transaction.current().execute(new TransactionBody() {
            @Override
            public Object execute() throws Exception {
                assertEquals(1, Transaction.current().getOpenTransactionCount());
                assertTrue(Transaction.current().isRunning());

                Transaction.current().execute(new TransactionBody() {
                    @Override
                    public Object execute() {
                        assertEquals(2, Transaction.current().getOpenTransactionCount());
                        assertTrue(Transaction.current().isRunning());
                        return null;
                    }
                });

                return null;
            }
        });

        assertEquals(0, Transaction.current().getOpenTransactionCount());
        assertFalse(Transaction.current().isRunning());
    }

    @Test
    public void testNestedTransactionWithCreate() throws Exception {
        for (int i = 0; i < 10; i++) {
            Transaction.current().begin();
            try {
                final MediaFormat format3 = MediaManager.getInstance().createMediaFormat("testFormat3_tx6");
                MediaManager.getInstance().createMedia("testMedia1WithFormat3_tx", format3);
            } finally {
                Transaction.current().rollback();
            }
        }
    }

    @Test
    public void testNestedTAError() throws Exception {

        final AtomicBoolean storeCalled = new AtomicBoolean();
        final AtomicBoolean commitCalled = new AtomicBoolean();
        final AtomicBoolean rollbackCalled = new AtomicBoolean();

        final Transaction transaction = new DefaultTransaction() {
            @Override
            public void rollback() throws TransactionException {
                rollbackCalled.set(true);
                super.rollback();
            }

            @Override
            public void commit() throws TransactionException {
                commitCalled.set(true);
                super.commit();
            }
        };
        transaction.enableDelayedStore(true);

        final EntityInstanceContext eCtx = new EntityInstanceContext() {

            @Override
            public ItemDeployment getItemDeployment() {
                return null;
            }

            @Override
            public PK getPK() {
                return PK.NULL_PK;
            }

            @Override
            public PersistencePool getPersistencePool() {
                return null;
            }

            @Override
            public void setPK(final PK pk) {
                // mock
            }
        };

        final EntityInstance mockEntity = new EntityInstance() {
            final EntityInstanceContext ctx = eCtx;

            @Override
            public PK ejbFindByPrimaryKey(final PK pkValue) throws YObjectNotFoundException {
                return null;
            }

            @Override
            public void ejbLoad() {
                // mock
            }

            @Override
            public void ejbRemove() {
                // mock
            }

            @Override
            public void ejbStore() {
                storeCalled.set(true);
                throw new IllegalArgumentException("let's rollback ;)");
            }

            @Override
            public EntityInstanceContext getEntityContext() {
                return ctx;
            }

            @Override
            public boolean needsStoring() {
                return true;
            }

            @Override
            public void setEntityContext(final EntityInstanceContext ctx) {
                // mock
            }

            @Override
            public void setNeedsStoring(final boolean needs) {
                // mock
            }

        };

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final PrintStream printstream = new PrintStream(bos);

        final PrintStream err = System.err;

        final AtomicReference<Title> itemRef = new AtomicReference<Title>();

        try {
            System.setErr(printstream);

            // outer TA
            transaction.execute(new TransactionBody() {
                @Override
                public Object execute() throws Exception {
                    // inner TA
                    transaction.execute(new TransactionBody() {
                        @Override
                        public Object execute() throws Exception {
                            itemRef.set(UserManager.getInstance().createTitle("T" + System.currentTimeMillis()));

                            // inject mock entity to call ejbStore upon -> throws exception
                            transaction.registerEntityInstance(mockEntity);

                            return null;
                        }

                    });
                    return null;
                }

            });
            fail("IllegalArgumentException expected");
        } catch (final IllegalArgumentException ex) {
            assertTrue(storeCalled.get());
            assertEquals("let's rollback ;)", ex.getMessage());

            assertFalse(transaction.isRunning());
            assertEquals(0, transaction.getOpenTransactionCount());
            assertNotNull(itemRef.get());
            assertFalse(itemRef.get().isAlive());

            final String errorLog = new String(bos.toByteArray());

            assertFalse(errorLog.contains("no transaction running"));
        } catch (final Exception e) {
            fail("unexpected error " + e.getMessage());
        } finally {
            System.setErr(err);
        }
    }

    @Test
    public void testPermittedExceptions() throws Exception {
        final Title title = UserManager.getInstance().createTitle("t4tx");
        title.setName("foo");
        assertEquals("foo", title.getName());

        // test normal behavior
        try {
            Transaction.current().execute(new TransactionBody() {

                @Override
                public Object execute() throws Exception {
                    title.setName("bar");
                    assertEquals("bar", title.getName());
                    throw new IllegalArgumentException();
                }
            });
            fail("IllegalArgumentException expected");
        } catch (final Exception e) {
            assertTrue(e instanceof IllegalArgumentException);
        }
        // change has been rolled back
        assertEquals("foo", title.getName());

        // test new behavior
        try {
            Transaction.current().execute(new TransactionBody() {

                @Override
                public Object execute() throws Exception {
                    title.setName("bar");
                    assertEquals("bar", title.getName());
                    throw new IllegalArgumentException();
                }
            }, IllegalArgumentException.class, JaloSystemException.class);
            fail("IllegalArgumentException expected");
        } catch (final Exception e) {
            assertTrue(e instanceof IllegalArgumentException);
        }
        assertFalse("found transaction still running", Transaction.current().isRunning());
        // change has *not* been rolled back
        assertEquals("bar", title.getName());

        // test success case for new behavior too
        Transaction.current().execute(new TransactionBody() {

            @Override
            public Object execute() throws Exception {
                title.setName("blubb");
                assertEquals("blubb", title.getName());
                return null;
            }
        }, IllegalArgumentException.class, JaloSystemException.class);
        assertFalse("found transaction still running", Transaction.current().isRunning());
        // change has *not* been rolled back
        assertEquals("blubb", title.getName());
    }

    @Test
    public void testToException() {
        final ConsistencyCheckException businessExceptionSubClass = new ConsistencyCheckException("a", 0);
        final JaloBusinessException businessException = new JaloBusinessException("a", 0);
        final RuntimeException runtimeException = new RuntimeException();
        final Exception exception = new Exception();
        final Error error = new OutOfMemoryError();
        final Throwable throwable = new Throwable();

        //Business exception should be returned from the method
        {
            final JaloBusinessException result = Transaction.toException(businessException,
                    JaloBusinessException.class);
            assertTrue(businessException == result);
        }

        //Subclass of business exception should be returned from the method (return type is the business exception)
        {
            final JaloBusinessException result = Transaction.toException(businessExceptionSubClass,
                    JaloBusinessException.class);
            assertTrue(businessExceptionSubClass == result);
        }

        //This should not match and throw new RuntimeException
        {
            try {
                Transaction.toException(businessException, ConsistencyCheckException.class);
            } catch (final Throwable t) {
                assertEquals(RuntimeException.class, t.getClass());
                assertTrue(businessException == t.getCause());
            }
        }

        {
            try {
                Transaction.toException(runtimeException, JaloBusinessException.class);
            } catch (final Throwable t) {
                assertEquals(runtimeException, t);
            }
        }
        {
            try {
                Transaction.toException(exception, JaloBusinessException.class);
            } catch (final Throwable t) {
                assertEquals(RuntimeException.class, t.getClass());
                assertTrue(exception == t.getCause());
            }
        }
        {
            try {
                Transaction.toException(error, JaloBusinessException.class);
            } catch (final Throwable t) {
                assertEquals(error, t);
            }
        }
        {
            try {
                Transaction.toException(throwable, JaloBusinessException.class);
            } catch (final Throwable t) {
                assertEquals(RuntimeException.class, t.getClass());
                assertTrue(throwable == t.getCause());
            }
        }
    }

    // PLA-8819
    @Test
    public void testManualRollbackNoException() throws ConsistencyCheckException {
        final Transaction tx = Transaction.current();

        PK pk = null;

        tx.begin();

        final Title title = UserManager.getInstance().createTitle("Foo" + System.nanoTime());
        pk = title.getPK();
        assertTrue(title.isAlive());

        tx.rollback(); // no exception shall be thrown here !!!

        try {
            jaloSession.getItem(pk);
            fail("rollbacked title " + pk + " still alive after rollback!");
        } catch (final JaloItemNotFoundException e) {
            // fine
        }
    }

    // PLA-11600
    @Test
    public void testWrongOpenTxCountAfterErrorDuringRollback() {
        final ErrorInUnsetTxBoundConnectionTransactionDummy tx = new ErrorInUnsetTxBoundConnectionTransactionDummy();
        TestUtils.disableFileAnalyzer(
                "expecting two exceptions from TransactionTest.testWrongOpenTxCountAfterErrorDuringRollback");
        try {
            tx.execute(new TransactionBody() {
                @Override
                public Object execute() throws Exception {
                    throw new IllegalStateException("break it!");
                }
            });
        } catch (final Exception e) {
            assertTrue(e instanceof IllegalStateException);
            assertEquals("break it!", e.getMessage());
        } finally {
            TestUtils.enableFileAnalyzer();
        }
        assertFalse(tx.isRunning());
        assertEquals(0, tx.getOpenTransactionCount());

        assertNotSame(tx, Transaction.current());
    }

    private static class ErrorInUnsetTxBoundConnectionTransactionDummy extends DefaultTransaction {
        @Override
        protected void unsetTxBoundConnection() {
            throw new RuntimeException("foo");
        }

        @Override
        public boolean isCurrent() {
            // need to fake it since we do not want to become current one for real
            return true;
        }

        @Override
        protected void setAsCurrent() {
            // don't want that
        }
    }

}