org.springframework.orm.hibernate3.HibernateJtaTransactionTests.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.orm.hibernate3.HibernateJtaTransactionTests.java

Source

/*
 * Copyright 2002-2013 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.orm.hibernate3;

import java.util.ArrayList;
import java.util.List;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import org.junit.After;
import org.junit.Test;
import org.mockito.InOrder;

import org.springframework.dao.DataAccessException;
import org.springframework.tests.transaction.MockJtaTransaction;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;

import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;

/**
 * @author Juergen Hoeller
 * @author Phillip Webb
 * @since 05.03.2005
 */
public class HibernateJtaTransactionTests {

    @After
    public void tearDown() {
        assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
        assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
        assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
        assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
    }

    @Test
    public void testJtaTransactionCommit() throws Exception {
        doTestJtaTransactionCommit(Status.STATUS_NO_TRANSACTION, false);
    }

    @Test
    public void testJtaTransactionCommitWithReadOnly() throws Exception {
        doTestJtaTransactionCommit(Status.STATUS_NO_TRANSACTION, true);
    }

    @Test
    public void testJtaTransactionCommitWithExisting() throws Exception {
        doTestJtaTransactionCommit(Status.STATUS_ACTIVE, false);
    }

    @Test
    public void testJtaTransactionCommitWithExistingAndReadOnly() throws Exception {
        doTestJtaTransactionCommit(Status.STATUS_ACTIVE, true);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void doTestJtaTransactionCommit(int status, final boolean readOnly) throws Exception {
        UserTransaction ut = mock(UserTransaction.class);
        final SessionFactory sf = mock(SessionFactory.class);
        final Session session = mock(Session.class);
        Query query = mock(Query.class);
        if (status == Status.STATUS_NO_TRANSACTION) {
            given(ut.getStatus()).willReturn(status, Status.STATUS_ACTIVE);
        } else {
            given(ut.getStatus()).willReturn(status);
        }

        final List list = new ArrayList();
        list.add("test");
        given(sf.openSession()).willReturn(session);
        given(session.getSessionFactory()).willReturn(sf);
        given(session.isOpen()).willReturn(true);
        given(session.createQuery("some query string")).willReturn(query);
        given(query.list()).willReturn(list);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        JtaTransactionManager ptm = new JtaTransactionManager(ut);
        TransactionTemplate tt = new TransactionTemplate(ptm);
        tt.setReadOnly(readOnly);
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));

        Object result = tt.execute(new TransactionCallback() {

            @Override
            public Object doInTransaction(TransactionStatus status) {
                try {
                    assertTrue("JTA synchronizations active",
                            TransactionSynchronizationManager.isSynchronizationActive());
                    assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
                    HibernateTemplate ht = new HibernateTemplate(sf);
                    ht.setExposeNativeSession(true);
                    ht.executeFind(new HibernateCallback() {

                        @Override
                        public Object doInHibernate(org.hibernate.Session sess) {
                            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                            assertEquals(session, sess);
                            return null;
                        }
                    });
                    ht = new HibernateTemplate(sf);
                    List htl = ht.executeFind(new HibernateCallback() {

                        @Override
                        public Object doInHibernate(org.hibernate.Session sess) {
                            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                            return sess.createQuery("some query string").list();
                        }
                    });
                    assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                    return htl;
                } catch (Error err) {
                    err.printStackTrace();
                    throw err;
                }
            }
        });

        assertTrue("Correct result list", result == list);
        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        if (status == Status.STATUS_NO_TRANSACTION) {
            InOrder ordered = inOrder(ut);
            ordered.verify(ut).begin();
            ordered.verify(ut).commit();
        }

        if (readOnly) {
            verify(session).setFlushMode(FlushMode.MANUAL);
        } else {
            verify(session).flush();
        }
        verify(session).close();
    }

    @Test
    public void testJtaTransactionCommitWithJtaTm() throws Exception {
        doTestJtaTransactionCommitWithJtaTm(Status.STATUS_NO_TRANSACTION);
    }

    @Test
    public void testJtaTransactionCommitWithJtaTmAndExisting() throws Exception {
        doTestJtaTransactionCommitWithJtaTm(Status.STATUS_ACTIVE);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void doTestJtaTransactionCommitWithJtaTm(int status) throws Exception {

        UserTransaction ut = mock(UserTransaction.class);
        if (status == Status.STATUS_NO_TRANSACTION) {
            given(ut.getStatus()).willReturn(status, status, Status.STATUS_ACTIVE);
        } else {
            given(ut.getStatus()).willReturn(status);
        }

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        given(tm.getTransaction()).willReturn(transaction);

        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session = mock(Session.class);
        given(sf.getTransactionManager()).willReturn(tm);
        given(sf.openSession()).willReturn(session);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        JtaTransactionManager ptm = new JtaTransactionManager(ut);
        TransactionTemplate tt = new TransactionTemplate(ptm);
        final List l = new ArrayList();
        l.add("test");
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));

        Object result = tt.execute(new TransactionCallback() {

            @Override
            public Object doInTransaction(TransactionStatus status) {
                try {
                    assertTrue("JTA synchronizations active",
                            TransactionSynchronizationManager.isSynchronizationActive());
                    assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
                    HibernateTemplate ht = new HibernateTemplate(sf);
                    ht.setExposeNativeSession(true);
                    List htl = ht.executeFind(new HibernateCallback() {

                        @Override
                        public Object doInHibernate(org.hibernate.Session sess) {
                            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                            assertEquals(session, sess);
                            return l;
                        }
                    });
                    ht = new HibernateTemplate(sf);
                    ht.setExposeNativeSession(true);
                    htl = ht.executeFind(new HibernateCallback() {

                        @Override
                        public Object doInHibernate(org.hibernate.Session sess) {
                            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                            assertEquals(session, sess);
                            return l;
                        }
                    });
                    assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                    return htl;
                } catch (Error err) {
                    err.printStackTrace();
                    throw err;
                }
            }
        });

        assertTrue("Correct result list", result == l);
        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        if (status == Status.STATUS_NO_TRANSACTION) {
            InOrder ordered = inOrder(ut);
            ordered.verify(ut).begin();
            ordered.verify(ut).commit();
        }
        verify(session).flush();
        verify(session).close();
    }

    @Test
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void testJtaTransactionWithFlushFailure() throws Exception {

        UserTransaction ut = mock(UserTransaction.class);
        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE);

        final SessionFactory sf = mock(SessionFactory.class);
        final Session session = mock(Session.class);
        given(sf.openSession()).willReturn(session);
        given(session.getSessionFactory()).willReturn(sf);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        JtaTransactionManager ptm = new JtaTransactionManager(ut);
        TransactionTemplate tt = new TransactionTemplate(ptm);
        final List l = new ArrayList();
        l.add("test");
        final HibernateException flushEx = new HibernateException("flush failure");
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        willThrow(flushEx).given(session).flush();

        try {
            tt.execute(new TransactionCallback() {

                @Override
                public Object doInTransaction(TransactionStatus status) {
                    try {
                        assertTrue("JTA synchronizations active",
                                TransactionSynchronizationManager.isSynchronizationActive());
                        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
                        HibernateTemplate ht = new HibernateTemplate(sf);
                        ht.setExposeNativeSession(true);
                        List htl = ht.executeFind(new HibernateCallback() {

                            @Override
                            public Object doInHibernate(org.hibernate.Session sess) {
                                assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                                assertEquals(session, sess);
                                return l;
                            }
                        });
                        ht = new HibernateTemplate(sf);
                        ht.setExposeNativeSession(true);
                        htl = ht.executeFind(new HibernateCallback() {

                            @Override
                            public Object doInHibernate(org.hibernate.Session sess) {
                                assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                                assertEquals(session, sess);
                                return l;
                            }
                        });
                        assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                        return htl;
                    } catch (Error err) {
                        err.printStackTrace();
                        throw err;
                    }
                }
            });
        } catch (DataAccessException ex) {
            // expected
            assertTrue(flushEx == ex.getCause());
        }

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        verify(ut).begin();
        verify(ut).rollback();
        verify(session).close();
    }

    @Test
    public void testJtaTransactionRollback() throws Exception {
        doTestJtaTransactionRollback(false);
    }

    @Test
    public void testJtaTransactionRollbackWithFlush() throws Exception {
        doTestJtaTransactionRollback(true);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void doTestJtaTransactionRollback(final boolean flush) throws Exception {

        UserTransaction ut = mock(UserTransaction.class);
        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE);

        final SessionFactory sf = mock(SessionFactory.class);
        final Session session = mock(Session.class);
        given(sf.openSession()).willReturn(session);
        given(session.getSessionFactory()).willReturn(sf);

        JtaTransactionManager ptm = new JtaTransactionManager(ut);
        TransactionTemplate tt = new TransactionTemplate(ptm);
        final List l = new ArrayList();
        l.add("test");
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        Object result = tt.execute(new TransactionCallback() {
            @Override
            public Object doInTransaction(TransactionStatus status) {
                try {
                    assertTrue("JTA synchronizations active",
                            TransactionSynchronizationManager.isSynchronizationActive());
                    HibernateTemplate ht = new HibernateTemplate(sf);
                    List htl = ht.executeFind(new HibernateCallback() {

                        @Override
                        public Object doInHibernate(org.hibernate.Session session) {
                            return l;
                        }
                    });
                    if (flush) {
                        status.flush();
                    }
                    status.setRollbackOnly();
                    return htl;
                } catch (Error err) {
                    err.printStackTrace();
                    throw err;
                }
            }
        });
        assertTrue("Correct result list", result == l);
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        InOrder ordered = inOrder(ut);
        ordered.verify(ut).begin();
        ordered.verify(ut).rollback();
        if (flush) {
            verify(session).flush();
        }
        verify(session).close();
    }

    @Test
    public void testJtaTransactionCommitWithPreBound() throws Exception {
        doTestJtaTransactionCommitWithPreBound(false, false, false);
    }

    @Test
    public void testJtaTransactionCommitWithPreBoundAndReadOnly() throws Exception {
        doTestJtaTransactionCommitWithPreBound(false, false, true);
    }

    @Test
    public void testJtaTransactionCommitWithPreBoundAndFlushModeNever() throws Exception {
        doTestJtaTransactionCommitWithPreBound(false, true, false);
    }

    @Test
    public void testJtaTransactionCommitWithPreBoundAndFlushModeNeverAndReadOnly() throws Exception {
        doTestJtaTransactionCommitWithPreBound(false, true, true);
    }

    @Test
    public void testJtaTransactionCommitWithJtaTmAndPreBound() throws Exception {
        doTestJtaTransactionCommitWithPreBound(true, false, false);
    }

    @Test
    public void testJtaTransactionCommitWithJtaTmAndPreBoundAndReadOnly() throws Exception {
        doTestJtaTransactionCommitWithPreBound(true, false, true);
    }

    @Test
    public void testJtaTransactionCommitWithJtaTmAndPreBoundAndFlushModeNever() throws Exception {
        doTestJtaTransactionCommitWithPreBound(true, true, false);
    }

    @Test
    public void testJtaTransactionCommitWithJtaTmAndPreBoundAndFlushModeNeverAndReadOnly() throws Exception {
        doTestJtaTransactionCommitWithPreBound(true, true, true);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected void doTestJtaTransactionCommitWithPreBound(boolean jtaTm, final boolean flushNever,
            final boolean readOnly) throws Exception {

        UserTransaction ut = mock(UserTransaction.class);
        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE, Status.STATUS_ACTIVE);

        TransactionManager tm = mock(TransactionManager.class);
        if (jtaTm) {
            MockJtaTransaction transaction = new MockJtaTransaction();
            given(tm.getTransaction()).willReturn(transaction);
        }

        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final ExtendedSession session = mock(ExtendedSession.class);
        given(sf.getTransactionManager()).willReturn(jtaTm ? tm : null);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(flushNever ? FlushMode.MANUAL : FlushMode.AUTO);

        TransactionSynchronizationManager.bindResource(sf, new SessionHolder(session));
        try {
            JtaTransactionManager ptm = new JtaTransactionManager(ut);
            TransactionTemplate tt = new TransactionTemplate(ptm);
            tt.setReadOnly(readOnly);
            final List l = new ArrayList();
            l.add("test");
            assertTrue("JTA synchronizations not active",
                    !TransactionSynchronizationManager.isSynchronizationActive());
            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));

            Object result = tt.execute(new TransactionCallback() {

                @Override
                public Object doInTransaction(TransactionStatus status) {
                    try {
                        assertTrue("JTA synchronizations active",
                                TransactionSynchronizationManager.isSynchronizationActive());
                        assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                        HibernateTemplate ht = new HibernateTemplate(sf);
                        ht.setExposeNativeSession(true);
                        List htl = null;
                        for (int i = 0; i < 5; i++) {
                            htl = ht.executeFind(new HibernateCallback() {

                                @Override
                                public Object doInHibernate(org.hibernate.Session sess) {
                                    assertTrue("Has thread session",
                                            TransactionSynchronizationManager.hasResource(sf));
                                    assertEquals(session, sess);
                                    return l;
                                }
                            });
                            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                        }
                        return htl;
                    } catch (Error err) {
                        err.printStackTrace();
                        throw err;
                    }
                }
            });

            assertTrue("Correct result list", result == l);
            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
            assertTrue("JTA synchronizations not active",
                    !TransactionSynchronizationManager.isSynchronizationActive());
        } finally {
            TransactionSynchronizationManager.unbindResource(sf);
        }

        verify(ut).begin();
        verify(ut).commit();

        if (flushNever) {
            if (!readOnly) {
                InOrder ordered = inOrder(session);
                ordered.verify(session).setFlushMode(FlushMode.AUTO);
                ordered.verify(session).setFlushMode(FlushMode.MANUAL);
            }
        }
        if (!flushNever && !readOnly) {
            verify(session).flush();
        }
        verify(session).afterTransactionCompletion(true, null);
        verify(session).disconnect();
    }

    @Test
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void testJtaTransactionRollbackWithPreBound() throws Exception {

        UserTransaction ut = mock(UserTransaction.class);
        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE, Status.STATUS_ACTIVE,
                Status.STATUS_MARKED_ROLLBACK, Status.STATUS_MARKED_ROLLBACK);
        RollbackException rex = new RollbackException();
        willThrow(rex).given(ut).commit();

        final SessionFactory sf = mock(SessionFactory.class);

        final Session session = mock(Session.class);
        given(session.getSessionFactory()).willReturn(sf);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        TransactionSynchronizationManager.bindResource(sf, new SessionHolder(session));
        try {
            JtaTransactionManager ptm = new JtaTransactionManager(ut);
            final TransactionTemplate tt = new TransactionTemplate(ptm);
            assertTrue("JTA synchronizations not active",
                    !TransactionSynchronizationManager.isSynchronizationActive());
            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));

            tt.execute(new TransactionCallbackWithoutResult() {

                @Override
                public void doInTransactionWithoutResult(TransactionStatus status) {
                    tt.execute(new TransactionCallbackWithoutResult() {

                        @Override
                        public void doInTransactionWithoutResult(TransactionStatus status) {
                            status.setRollbackOnly();
                            try {
                                assertTrue("JTA synchronizations active",
                                        TransactionSynchronizationManager.isSynchronizationActive());
                                assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                                HibernateTemplate ht = new HibernateTemplate(sf);
                                ht.setExposeNativeSession(true);
                                for (int i = 0; i < 5; i++) {
                                    ht.execute(new HibernateCallback() {

                                        @Override
                                        public Object doInHibernate(org.hibernate.Session sess) {
                                            assertTrue("Has thread session",
                                                    TransactionSynchronizationManager.hasResource(sf));
                                            assertEquals(session, sess);
                                            return null;
                                        }
                                    });
                                    assertTrue("Has thread session",
                                            TransactionSynchronizationManager.hasResource(sf));
                                }
                            } catch (Error err) {
                                err.printStackTrace();
                                throw err;
                            }
                        }
                    });
                }
            });
            fail("Should have thrown UnexpectedRollbackException");
        } catch (UnexpectedRollbackException ex) {
            // expected
            assertEquals(rex, ex.getCause());
        } finally {
            TransactionSynchronizationManager.unbindResource(sf);
        }

        verify(ut).begin();
        verify(ut).setRollbackOnly();
        verify(session).flush();
        verify(session).disconnect();
        verify(session).clear();
    }

    @Test
    public void testJtaTransactionCommitWithRequiresNew() throws Exception {
        doTestJtaTransactionWithRequiresNew(false);
    }

    @Test
    public void testJtaTransactionRollbackWithRequiresNew() throws Exception {
        doTestJtaTransactionWithRequiresNew(true);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected void doTestJtaTransactionWithRequiresNew(final boolean rollback) throws Exception {

        UserTransaction ut = mock(UserTransaction.class);

        TransactionManager tm = mock(TransactionManager.class);
        javax.transaction.Transaction tx1 = mock(javax.transaction.Transaction.class);

        final SessionFactory sf = mock(SessionFactory.class);
        final Session session1 = mock(Session.class);
        final Session session2 = mock(Session.class);

        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE, Status.STATUS_ACTIVE,
                Status.STATUS_ACTIVE, Status.STATUS_ACTIVE, Status.STATUS_ACTIVE);
        given(tm.suspend()).willReturn(tx1);
        given(sf.openSession()).willReturn(session1, session2);
        given(session1.getSessionFactory()).willReturn(sf);
        given(session2.getSessionFactory()).willReturn(sf);
        given(session1.isOpen()).willReturn(true);
        given(session2.isOpen()).willReturn(true);
        given(session2.getFlushMode()).willReturn(FlushMode.AUTO);
        if (!rollback) {
            given(session1.getFlushMode()).willReturn(FlushMode.AUTO);
        }

        JtaTransactionManager ptm = new JtaTransactionManager();
        ptm.setUserTransaction(ut);
        ptm.setTransactionManager(tm);
        final TransactionTemplate tt = new TransactionTemplate(ptm);
        tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        try {
            tt.execute(new TransactionCallback() {

                @Override
                public Object doInTransaction(TransactionStatus status) {
                    org.hibernate.Session outerSession = SessionFactoryUtils.getSession(sf, false);
                    assertSame(session1, outerSession);
                    SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sf);
                    assertTrue("Has thread session", holder != null);
                    try {
                        tt.execute(new TransactionCallback() {

                            @Override
                            public Object doInTransaction(TransactionStatus status) {
                                org.hibernate.Session innerSession = SessionFactoryUtils.getSession(sf, false);
                                assertSame(session2, innerSession);
                                HibernateTemplate ht = new HibernateTemplate(sf);
                                ht.setFlushMode(HibernateTemplate.FLUSH_EAGER);
                                return ht.executeFind(new HibernateCallback() {

                                    @Override
                                    public Object doInHibernate(org.hibernate.Session innerSession) {
                                        if (rollback) {
                                            throw new HibernateException("");
                                        }
                                        return null;
                                    }
                                });
                            }
                        });
                        return null;
                    } finally {
                        assertTrue("Same thread session as before",
                                outerSession == SessionFactoryUtils.getSession(sf, false));
                    }
                }
            });
            if (rollback) {
                fail("Should have thrown DataAccessException");
            }
        } catch (DataAccessException ex) {
            if (!rollback) {
                throw ex;
            }
        } finally {
            assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        }

        verify(ut, times(2)).begin();
        verify(tm).resume(tx1);
        if (rollback) {
            verify(ut, times(2)).rollback();
        } else {
            verify(ut, times(2)).commit();
        }
        verify(session1).disconnect();
        verify(session1).close();
        if (!rollback) {
            verify(session1).flush();
            verify(session2, atLeastOnce()).flush();
        }
        verify(session2).close();
    }

    @Test
    public void testJtaTransactionWithRequiresNewAndSuspendException() throws Exception {
        doTestJtaTransactionWithRequiresNewAndException(true);
    }

    @Test
    public void testJtaTransactionWithRequiresNewAndBeginException() throws Exception {
        doTestJtaTransactionWithRequiresNewAndException(false);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected void doTestJtaTransactionWithRequiresNewAndException(boolean suspendException) throws Exception {

        UserTransaction ut = mock(UserTransaction.class);

        TransactionManager tm = mock(TransactionManager.class);
        javax.transaction.Transaction tx = mock(javax.transaction.Transaction.class);

        final SessionFactory sf = mock(SessionFactory.class);
        final Session session1 = mock(Session.class);

        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE, Status.STATUS_ACTIVE);
        if (suspendException) {
            given(tm.suspend()).willThrow(new SystemException());
        } else {
            given(tm.suspend()).willReturn(tx);
            willDoNothing().willThrow(new SystemException()).given(ut).begin();
        }

        given(sf.openSession()).willReturn(session1);
        given(session1.getSessionFactory()).willReturn(sf);

        JtaTransactionManager ptm = new JtaTransactionManager();
        ptm.setUserTransaction(ut);
        ptm.setTransactionManager(tm);
        final TransactionTemplate tt = new TransactionTemplate(ptm);
        tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        try {
            tt.execute(new TransactionCallback() {

                @Override
                public Object doInTransaction(TransactionStatus status) {
                    org.hibernate.Session outerSession = SessionFactoryUtils.getSession(sf, false);
                    assertSame(session1, outerSession);
                    SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sf);
                    assertTrue("Has thread session", holder != null);
                    tt.execute(new TransactionCallback() {

                        @Override
                        public Object doInTransaction(TransactionStatus status) {
                            return null;
                        }
                    });
                    return null;
                }
            });
            fail("Should have thrown TransactionException");
        } catch (TransactionException ex) {
            // expected
        } finally {
            assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        }

        verify(ut, atLeastOnce()).begin();
        if (!suspendException) {
            verify(tm).resume(tx);
        }
        verify(ut).rollback();
        verify(session1).disconnect();
        verify(session1).close();
    }

    @Test
    public void testJtaTransactionCommitWithRequiresNewAndJtaTm() throws Exception {
        doTestJtaTransactionWithRequiresNewAndJtaTm(false);
    }

    @Test
    public void testJtaTransactionRollbackWithRequiresNewAndJtaTm() throws Exception {
        doTestJtaTransactionWithRequiresNewAndJtaTm(true);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected void doTestJtaTransactionWithRequiresNewAndJtaTm(final boolean rollback) throws Exception {

        UserTransaction ut = mock(UserTransaction.class);

        TransactionManager tm = mock(TransactionManager.class);
        javax.transaction.Transaction tx1 = mock(javax.transaction.Transaction.class);

        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session1 = mock(Session.class);
        final Session session2 = mock(Session.class);

        MockJtaTransaction transaction1 = new MockJtaTransaction();
        MockJtaTransaction transaction2 = new MockJtaTransaction();
        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE, Status.STATUS_ACTIVE,
                Status.STATUS_ACTIVE, Status.STATUS_ACTIVE, Status.STATUS_ACTIVE);
        given(tm.getTransaction()).willReturn(transaction1);
        given(tm.suspend()).willReturn(tx1);
        given(tm.getTransaction()).willReturn(transaction2);
        given(sf.getTransactionManager()).willReturn(tm);
        given(sf.openSession()).willReturn(session1, session2);
        given(session1.isOpen()).willReturn(true);
        given(session2.isOpen()).willReturn(true);
        given(session1.getFlushMode()).willReturn(FlushMode.AUTO);
        given(session2.getFlushMode()).willReturn(FlushMode.AUTO);

        JtaTransactionManager ptm = new JtaTransactionManager();
        ptm.setUserTransaction(ut);
        ptm.setTransactionManager(tm);
        final TransactionTemplate tt = new TransactionTemplate(ptm);
        tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        try {
            tt.execute(new TransactionCallback() {

                @Override
                public Object doInTransaction(TransactionStatus status) {
                    org.hibernate.Session outerSession = SessionFactoryUtils.getSession(sf, false);
                    assertSame(session1, outerSession);
                    SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sf);
                    assertTrue("Has thread session", holder != null);
                    try {
                        tt.execute(new TransactionCallback() {

                            @Override
                            public Object doInTransaction(TransactionStatus status) {
                                org.hibernate.Session innerSession = SessionFactoryUtils.getSession(sf, false);
                                assertSame(session2, innerSession);
                                HibernateTemplate ht = new HibernateTemplate(sf);
                                ht.setFlushMode(HibernateTemplate.FLUSH_EAGER);
                                return ht.executeFind(new HibernateCallback() {

                                    @Override
                                    public Object doInHibernate(org.hibernate.Session innerSession) {
                                        if (rollback) {
                                            throw new HibernateException("");
                                        }
                                        return null;
                                    }
                                });
                            }
                        });
                        return null;
                    } finally {
                        assertTrue("Same thread session as before",
                                outerSession == SessionFactoryUtils.getSession(sf, false));
                    }
                }
            });
            if (rollback) {
                fail("Should have thrown DataAccessException");
            }
        } catch (DataAccessException ex) {
            if (!rollback) {
                throw ex;
            }
        } finally {
            assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        }

        verify(ut, times(2)).begin();
        verify(tm).resume(tx1);
        if (rollback) {
            verify(ut, times(2)).rollback();
        } else {
            verify(ut, times(2)).commit();
            verify(session1).flush();
            verify(session2, times(2)).flush();
        }
        verify(session1).disconnect();
        verify(session1).close();
        verify(session2).close();
    }

    @Test
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void testTransactionWithPropagationSupports() throws Exception {

        UserTransaction ut = mock(UserTransaction.class);
        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE);

        final SessionFactory sf = mock(SessionFactory.class);
        final Session session = mock(Session.class);

        given(sf.openSession()).willReturn(session);
        given(session.getSessionFactory()).willReturn(sf);
        given(session.getFlushMode()).willReturn(FlushMode.MANUAL);

        JtaTransactionManager tm = new JtaTransactionManager(ut);
        TransactionTemplate tt = new TransactionTemplate(tm);
        tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));

        tt.execute(new TransactionCallback() {

            @Override
            public Object doInTransaction(TransactionStatus status) {
                assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
                assertTrue("Is not new transaction", !status.isNewTransaction());
                assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
                assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
                HibernateTemplate ht = new HibernateTemplate(sf);
                ht.setFlushMode(HibernateTemplate.FLUSH_EAGER);
                ht.execute(new HibernateCallback() {

                    @Override
                    public Object doInHibernate(org.hibernate.Session session) {
                        return null;
                    }
                });
                assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                return null;
            }
        });

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        InOrder ordered = inOrder(session);
        ordered.verify(session).setFlushMode(FlushMode.AUTO);
        ordered.verify(session).flush();
        ordered.verify(session).setFlushMode(FlushMode.MANUAL);
        ordered.verify(session).close();
    }

    @Test
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void testTransactionWithPropagationSupportsAndInnerTransaction() throws Exception {

        UserTransaction ut = mock(UserTransaction.class);
        given(ut.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE);

        final SessionFactory sf = mock(SessionFactory.class);
        final Session session = mock(Session.class);

        given(sf.openSession()).willReturn(session);
        given(session.getSessionFactory()).willReturn(sf);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        JtaTransactionManager tm = new JtaTransactionManager(ut);
        TransactionTemplate tt = new TransactionTemplate(tm);
        tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
        final TransactionTemplate tt2 = new TransactionTemplate(tm);
        tt2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        tt.execute(new TransactionCallback() {

            @Override
            public Object doInTransaction(TransactionStatus status) {
                assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
                assertTrue("Is not new transaction", !status.isNewTransaction());
                assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
                assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
                HibernateTemplate ht = new HibernateTemplate(sf);
                ht.setFlushMode(HibernateTemplate.FLUSH_EAGER);
                ht.execute(new HibernateCallback() {

                    @Override
                    public Object doInHibernate(org.hibernate.Session session) {
                        return null;
                    }
                });
                assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                tt2.execute(new TransactionCallback() {

                    @Override
                    public Object doInTransaction(TransactionStatus status) {
                        HibernateTemplate ht = new HibernateTemplate(sf);
                        ht.setFlushMode(HibernateTemplate.FLUSH_EAGER);
                        return ht.executeFind(new HibernateCallback() {

                            @Override
                            public Object doInHibernate(org.hibernate.Session session) {
                                assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
                                // assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
                                return null;
                            }
                        });
                    }
                });
                assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
                assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
                return null;
            }
        });
        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        verify(session, times(3)).flush();
        verify(session).close();
    }

    @Test
    @SuppressWarnings("rawtypes")
    public void testJtaSessionSynchronization() throws Exception {

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        given(tm.getTransaction()).willReturn(transaction);
        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session = mock(Session.class);
        given(sf.openSession()).willReturn(session);
        given(sf.getTransactionManager()).willReturn(tm);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        HibernateTemplate ht = new HibernateTemplate(sf);
        ht.setExposeNativeSession(true);
        for (int i = 0; i < 5; i++) {
            ht.executeFind(new HibernateCallback() {

                @Override
                public Object doInHibernate(org.hibernate.Session sess) {
                    assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                    assertEquals(session, sess);
                    return null;
                }
            });
        }

        Synchronization synchronization = transaction.getSynchronization();
        assertTrue("JTA synchronization registered", synchronization != null);
        synchronization.beforeCompletion();
        synchronization.afterCompletion(Status.STATUS_COMMITTED);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        verify(session).flush();
        verify(session).close();
    }

    @Test
    @SuppressWarnings("rawtypes")
    public void testJtaSessionSynchronizationWithRollback() throws Exception {

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        given(tm.getTransaction()).willReturn(transaction);
        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session = mock(Session.class);
        given(sf.openSession()).willReturn(session);
        given(sf.getTransactionManager()).willReturn(tm);
        given(session.isOpen()).willReturn(true);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        HibernateTemplate ht = new HibernateTemplate(sf);
        ht.setExposeNativeSession(true);
        for (int i = 0; i < 5; i++) {
            ht.executeFind(new HibernateCallback() {

                @Override
                public Object doInHibernate(org.hibernate.Session sess) {
                    assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                    assertEquals(session, sess);
                    return null;
                }
            });
        }

        Synchronization synchronization = transaction.getSynchronization();
        assertTrue("JTA synchronization registered", synchronization != null);
        synchronization.afterCompletion(Status.STATUS_ROLLEDBACK);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        verify(session).close();
    }

    @Test
    @SuppressWarnings("rawtypes")
    public void testJtaSessionSynchronizationWithRollbackByOtherThread() throws Exception {

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        given(tm.getTransaction()).willReturn(transaction);
        given(tm.getStatus()).willReturn(Status.STATUS_NO_TRANSACTION);
        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session = mock(Session.class);
        given(sf.openSession()).willReturn(session);
        given(sf.getTransactionManager()).willReturn(tm);
        given(session.isOpen()).willReturn(true);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        final HibernateTemplate ht = new HibernateTemplate(sf);
        ht.setExposeNativeSession(true);
        for (int i = 0; i < 5; i++) {
            ht.executeFind(new HibernateCallback() {

                @Override
                public Object doInHibernate(org.hibernate.Session sess) {
                    assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                    assertEquals(session, sess);
                    return null;
                }
            });
        }

        final Synchronization synchronization = transaction.getSynchronization();
        assertTrue("JTA synchronization registered", synchronization != null);
        Thread thread = new Thread() {

            @Override
            public void run() {
                synchronization.afterCompletion(Status.STATUS_ROLLEDBACK);
            }
        };
        thread.start();
        thread.join();

        assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        TransactionTemplate tt = new TransactionTemplate(new JtaTransactionManager(tm));
        tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
        tt.setReadOnly(true);
        tt.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                assertTrue("JTA synchronizations active",
                        TransactionSynchronizationManager.isSynchronizationActive());
                for (int i = 0; i < 5; i++) {
                    ht.executeFind(new HibernateCallback() {

                        @Override
                        public Object doInHibernate(org.hibernate.Session sess) {
                            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                            assertEquals(session, sess);
                            return null;
                        }
                    });
                }
            }
        });

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        verify(session).setFlushMode(FlushMode.MANUAL);
        verify(session, times(2)).close();
    }

    @Test
    @SuppressWarnings("rawtypes")
    public void testJtaSessionSynchronizationWithFlushFailure() throws Exception {

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        given(tm.getTransaction()).willReturn(transaction);
        final HibernateException flushEx = new HibernateException("flush failure");
        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session = mock(Session.class);
        given(sf.openSession()).willReturn(session);
        given(sf.getTransactionManager()).willReturn(tm);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);
        willThrow(flushEx).given(session).flush();

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        HibernateTemplate ht = new HibernateTemplate(sf);
        ht.setExposeNativeSession(true);
        for (int i = 0; i < 5; i++) {
            ht.executeFind(new HibernateCallback() {

                @Override
                public Object doInHibernate(org.hibernate.Session sess) {
                    assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                    assertEquals(session, sess);
                    return null;
                }
            });
        }

        Synchronization synchronization = transaction.getSynchronization();
        assertTrue("JTA synchronization registered", synchronization != null);
        try {
            synchronization.beforeCompletion();
            fail("Should have thrown HibernateSystemException");
        } catch (HibernateSystemException ex) {
            assertSame(flushEx, ex.getCause());
        }
        synchronization.afterCompletion(Status.STATUS_ROLLEDBACK);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        verify(tm).setRollbackOnly();
        verify(session).close();
    }

    @Test
    @SuppressWarnings("rawtypes")
    public void testJtaSessionSynchronizationWithSuspendedTransaction() throws Exception {

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction1 = new MockJtaTransaction();
        MockJtaTransaction transaction2 = new MockJtaTransaction();
        given(tm.getTransaction()).willReturn(transaction1, transaction1, transaction2, transaction2, transaction2);

        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session1 = mock(Session.class);
        final Session session2 = mock(Session.class);
        given(sf.openSession()).willReturn(session1, session2);
        given(sf.getTransactionManager()).willReturn(tm);
        given(session1.getFlushMode()).willReturn(FlushMode.AUTO);
        given(session2.getFlushMode()).willReturn(FlushMode.AUTO);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        HibernateTemplate ht = new HibernateTemplate(sf);
        ht.setExposeNativeSession(true);
        ht.executeFind(new HibernateCallback() {

            @Override
            public Object doInHibernate(org.hibernate.Session sess) {
                assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                assertEquals(session1, sess);
                return null;
            }
        });
        ht.executeFind(new HibernateCallback() {

            @Override
            public Object doInHibernate(org.hibernate.Session sess) {
                assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                assertEquals(session2, sess);
                return null;
            }
        });

        Synchronization synchronization2 = transaction2.getSynchronization();
        assertTrue("JTA synchronization registered", synchronization2 != null);
        synchronization2.beforeCompletion();
        synchronization2.afterCompletion(Status.STATUS_COMMITTED);

        Synchronization synchronization1 = transaction1.getSynchronization();
        assertTrue("JTA synchronization registered", synchronization1 != null);
        synchronization1.beforeCompletion();
        synchronization1.afterCompletion(Status.STATUS_COMMITTED);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        verify(session1).flush();
        verify(session2).flush();
        verify(session1).close();
        verify(session2).close();
    }

    @Test
    @SuppressWarnings("rawtypes")
    public void testJtaSessionSynchronizationWithNonSessionFactoryImplementor() throws Exception {

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        given(tm.getTransaction()).willReturn(transaction);
        final SessionFactory sf = mock(SessionFactory.class);
        final Session session = mock(Session.class);
        final SessionFactoryImplementor sfi = mock(SessionFactoryImplementor.class);
        given(sf.openSession()).willReturn(session);
        given(session.getSessionFactory()).willReturn(sfi);
        given(sfi.getTransactionManager()).willReturn(tm);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        HibernateTemplate ht = new HibernateTemplate(sf);
        ht.setExposeNativeSession(true);
        for (int i = 0; i < 5; i++) {
            ht.executeFind(new HibernateCallback() {

                @Override
                public Object doInHibernate(org.hibernate.Session sess) {
                    assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                    assertEquals(session, sess);
                    return null;
                }
            });
        }

        Synchronization synchronization = transaction.getSynchronization();
        assertTrue("JTA Synchronization registered", synchronization != null);
        synchronization.beforeCompletion();
        synchronization.afterCompletion(Status.STATUS_COMMITTED);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        verify(session).flush();
        verify(session).close();
    }

    @Test
    @SuppressWarnings("rawtypes")
    public void testJtaSessionSynchronizationWithSpringTransactionLaterOn() throws Exception {
        UserTransaction ut = mock(UserTransaction.class);

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        given(ut.getStatus()).willReturn(Status.STATUS_ACTIVE);
        given(tm.getTransaction()).willReturn(transaction);
        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session = mock(Session.class);
        given(sf.openSession()).willReturn(session);
        given(sf.getTransactionManager()).willReturn(tm);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        final HibernateTemplate ht = new HibernateTemplate(sf);
        ht.setExposeNativeSession(true);
        for (int i = 0; i < 2; i++) {
            ht.executeFind(new HibernateCallback() {

                @Override
                public Object doInHibernate(org.hibernate.Session sess) {
                    assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                    assertEquals(session, sess);
                    return null;
                }
            });
        }

        TransactionTemplate tt = new TransactionTemplate(new JtaTransactionManager(ut));
        tt.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                for (int i = 2; i < 5; i++) {
                    ht.executeFind(new HibernateCallback() {

                        @Override
                        public Object doInHibernate(org.hibernate.Session sess) {
                            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                            assertEquals(session, sess);
                            return null;
                        }
                    });
                }
            }
        });

        Synchronization synchronization = transaction.getSynchronization();
        assertTrue("JTA synchronization registered", synchronization != null);
        synchronization.beforeCompletion();
        synchronization.afterCompletion(Status.STATUS_COMMITTED);

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

        verify(session).flush();
        verify(session).close();
    }

    @Test
    public void testJtaSessionSynchronizationWithPreBound() throws Exception {
        doTestJtaSessionSynchronizationWithPreBound(false);
    }

    @Test
    public void testJtaJtaSessionSynchronizationWithPreBoundAndFlushNever() throws Exception {
        doTestJtaSessionSynchronizationWithPreBound(true);
    }

    @SuppressWarnings("rawtypes")
    private void doTestJtaSessionSynchronizationWithPreBound(boolean flushNever) throws Exception {

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        given(tm.getTransaction()).willReturn(transaction);
        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session = mock(Session.class);
        given(sf.getTransactionManager()).willReturn(tm);
        given(session.isOpen()).willReturn(true);
        if (flushNever) {
            given(session.getFlushMode()).willReturn(FlushMode.MANUAL, FlushMode.AUTO, FlushMode.MANUAL);
        } else {
            given(session.getFlushMode()).willReturn(FlushMode.AUTO);
        }

        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
        TransactionSynchronizationManager.bindResource(sf, new SessionHolder(session));
        try {
            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
            HibernateTemplate ht = new HibernateTemplate(sf);
            ht.setExposeNativeSession(true);
            for (int i = 0; i < 5; i++) {
                ht.executeFind(new HibernateCallback() {

                    @Override
                    public Object doInHibernate(org.hibernate.Session sess) {
                        assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                        assertEquals(session, sess);
                        return null;
                    }
                });
            }

            Synchronization synchronization = transaction.getSynchronization();
            assertTrue("JTA synchronization registered", synchronization != null);
            synchronization.beforeCompletion();
            synchronization.afterCompletion(Status.STATUS_COMMITTED);
            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
        } finally {
            TransactionSynchronizationManager.unbindResource(sf);
        }
        assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));

        InOrder ordered = inOrder(session);
        if (flushNever) {
            ordered.verify(session).setFlushMode(FlushMode.AUTO);
            ordered.verify(session).setFlushMode(FlushMode.MANUAL);
        } else {
            ordered.verify(session).flush();
        }
        ordered.verify(session).disconnect();
    }

    @Test
    @SuppressWarnings("rawtypes")
    public void testJtaSessionSynchronizationWithRemoteTransaction() throws Exception {

        TransactionManager tm = mock(TransactionManager.class);
        MockJtaTransaction transaction = new MockJtaTransaction();
        final SessionFactoryImplementor sf = mock(SessionFactoryImplementor.class);
        final Session session = mock(Session.class);
        given(tm.getTransaction()).willReturn(transaction);
        given(sf.openSession()).willReturn(session);
        given(sf.getTransactionManager()).willReturn(tm);
        given(session.isOpen()).willReturn(true);
        given(session.getFlushMode()).willReturn(FlushMode.AUTO);

        for (int j = 0; j < 2; j++) {

            if (j == 0) {
                assertTrue("Hasn't thread session", !TransactionSynchronizationManager.hasResource(sf));
            } else {
                assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
            }

            HibernateTemplate ht = new HibernateTemplate(sf);
            ht.setExposeNativeSession(true);
            for (int i = 0; i < 5; i++) {
                ht.executeFind(new HibernateCallback() {

                    @Override
                    public Object doInHibernate(org.hibernate.Session sess) {
                        assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
                        assertEquals(session, sess);
                        return null;
                    }
                });
            }

            final Synchronization synchronization = transaction.getSynchronization();
            assertTrue("JTA synchronization registered", synchronization != null);

            // Call synchronization in a new thread, to simulate a
            // synchronization
            // triggered by a new remote call from a remote transaction
            // coordinator.
            Thread synch = new Thread() {

                @Override
                public void run() {
                    synchronization.beforeCompletion();
                    synchronization.afterCompletion(Status.STATUS_COMMITTED);
                }
            };
            synch.start();
            synch.join();

            assertTrue("Has thread session", TransactionSynchronizationManager.hasResource(sf));
            SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sf);
            assertTrue("Thread session holder empty", sessionHolder.isEmpty());
            assertTrue("JTA synchronizations not active",
                    !TransactionSynchronizationManager.isSynchronizationActive());
        }

        verify(session, times(2)).flush();
        verify(session, times(2)).close();
        TransactionSynchronizationManager.unbindResource(sf);
    }

    /**
     * Interface that combines Hibernate's Session and SessionImplementor
     * interface. Necessary for creating a mock that implements both interfaces.
     * Note: Hibernate 3.1's SessionImplementor interface does not extend
     * Session anymore.
     */
    public static interface ExtendedSession extends Session, SessionImplementor {

    }

}