/*
 * Copyright 2002-2007 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.jdbc.datasource;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

import junit.framework.TestCase;
import org.easymock.MockControl;

import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.jdbc.datasource.lookup.BeanFactoryDataSourceLookup;
import org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.transaction.jta.JtaTransactionObject;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * @author Juergen Hoeller
 * @since 17.10.2005
 */
public class DataSourceJtaTransactionTests extends TestCase {

	public void testJtaTransactionCommit() throws Exception {
		doTestJtaTransaction(false);
	}

	public void testJtaTransactionRollback() throws Exception {
		doTestJtaTransaction(true);
	}

	private void doTestJtaTransaction(final boolean rollback) throws Exception {
		MockControl utControl = MockControl.createControl(UserTransaction.class);
		UserTransaction ut = (UserTransaction) utControl.getMock();
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_ACTIVE, 1);
		ut.begin();
		utControl.setVoidCallable(1);
		if (rollback) {
			ut.rollback();
		}
		else {
			ut.getStatus();
			utControl.setReturnValue(Status.STATUS_ACTIVE, 1);
			ut.commit();
		}
		utControl.setVoidCallable(1);
		utControl.replay();

		MockControl dsControl = MockControl.createControl(DataSource.class);
		final DataSource ds = (DataSource) dsControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();
		ds.getConnection();
		dsControl.setReturnValue(con, 1);
		con.close();
		conControl.setVoidCallable(1);
		conControl.replay();
		dsControl.replay();

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

		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
				assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
				assertTrue("JTA synchronizations active", TransactionSynchronizationManager.isSynchronizationActive());
				assertTrue("Is new transaction", status.isNewTransaction());

				Connection c = DataSourceUtils.getConnection(ds);
				assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
				DataSourceUtils.releaseConnection(c, ds);

				c = DataSourceUtils.getConnection(ds);
				assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
				DataSourceUtils.releaseConnection(c, ds);

				if (rollback) {
					status.setRollbackOnly();
				}
			}
		});

		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
		assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
		dsControl.verify();
		conControl.verify();
		utControl.verify();
	}

	public void testJtaTransactionCommitWithPropagationRequiresNew() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(false, false, false, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithAccessAfterResume() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(false, false, true, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithOpenOuterConnection() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(false, true, false, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithOpenOuterConnectionAccessed() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(false, true, true, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithTransactionAwareDataSource() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(false, false, true, true);
	}

	public void testJtaTransactionRollbackWithPropagationRequiresNew() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(true, false, false, false);
	}

	public void testJtaTransactionRollbackWithPropagationRequiresNewWithAccessAfterResume() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(true, false, true, false);
	}

	public void testJtaTransactionRollbackWithPropagationRequiresNewWithOpenOuterConnection() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(true, true, false, false);
	}

	public void testJtaTransactionRollbackWithPropagationRequiresNewWithOpenOuterConnectionAccessed() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(true, true, true, false);
	}

	public void testJtaTransactionRollbackWithPropagationRequiresNewWithTransactionAwareDataSource() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNew(true, false, true, true);
	}

	private void doTestJtaTransactionWithPropagationRequiresNew(
			final boolean rollback, final boolean openOuterConnection, final boolean accessAfterResume,
			final boolean useTransactionAwareDataSource) throws Exception {

		MockControl utControl = MockControl.createControl(UserTransaction.class);
		UserTransaction ut = (UserTransaction) utControl.getMock();
		MockControl tmControl = MockControl.createControl(TransactionManager.class);
		TransactionManager tm = (TransactionManager) tmControl.getMock();
		MockControl txControl = MockControl.createControl(Transaction.class);
		Transaction tx = (Transaction) txControl.getMock();
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		ut.begin();
		utControl.setVoidCallable(1);
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_ACTIVE, 16);
		tm.suspend();
		tmControl.setReturnValue(tx, 5);
		ut.begin();
		utControl.setVoidCallable(5);
		ut.commit();
		utControl.setVoidCallable(5);
		tm.resume(tx);
		tmControl.setVoidCallable(5);
		if (rollback) {
			ut.rollback();
		}
		else {
			ut.getStatus();
			utControl.setReturnValue(Status.STATUS_ACTIVE, 1);
			ut.commit();
		}
		utControl.setVoidCallable(1);
		utControl.replay();
		tmControl.replay();

		final MockControl dsControl = MockControl.createControl(DataSource.class);
		final DataSource ds = (DataSource) dsControl.getMock();
		final MockControl conControl = MockControl.createControl(Connection.class);
		final Connection con = (Connection) conControl.getMock();
		ds.getConnection();
		dsControl.setReturnValue(con, 1);
		con.isReadOnly();
		conControl.setReturnValue(true, 1);
		if (!openOuterConnection) {
			con.close();
			conControl.setVoidCallable(1);
		}
		conControl.replay();
		dsControl.replay();

		final DataSource dsToUse = useTransactionAwareDataSource ?
				new TransactionAwareDataSourceProxy(ds) : ds;

		JtaTransactionManager ptm = new JtaTransactionManager(ut, tm);
		final TransactionTemplate tt = new TransactionTemplate(ptm);
		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
		assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
				assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
				assertTrue("JTA synchronizations active", TransactionSynchronizationManager.isSynchronizationActive());
				assertTrue("Is new transaction", status.isNewTransaction());

				Connection c = DataSourceUtils.getConnection(dsToUse);
				try {
					assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
					c.isReadOnly();
					DataSourceUtils.releaseConnection(c, dsToUse);

					c = DataSourceUtils.getConnection(dsToUse);
					assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
					if (!openOuterConnection) {
						DataSourceUtils.releaseConnection(c, dsToUse);
					}
				}
				catch (SQLException ex) {
				}

				for (int i = 0; i < 5; i++) {

					tt.execute(new TransactionCallbackWithoutResult() {
						protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
							assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
							assertTrue("JTA synchronizations active", TransactionSynchronizationManager.isSynchronizationActive());
							assertTrue("Is new transaction", status.isNewTransaction());

							try {
								dsControl.verify();
								conControl.verify();
								dsControl.reset();
								conControl.reset();
								ds.getConnection();
								dsControl.setReturnValue(con, 1);
								con.isReadOnly();
								conControl.setReturnValue(true, 1);
								con.close();
								conControl.setVoidCallable(1);
								dsControl.replay();
								conControl.replay();

								Connection c = DataSourceUtils.getConnection(dsToUse);
								c.isReadOnly();
								assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
								DataSourceUtils.releaseConnection(c, dsToUse);

								c = DataSourceUtils.getConnection(dsToUse);
								assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
								DataSourceUtils.releaseConnection(c, dsToUse);
							}
							catch (SQLException ex) {
							}
						}
					});

				}

				if (rollback) {
					status.setRollbackOnly();
				}

				if (accessAfterResume) {
					try {
						if (!openOuterConnection) {
							dsControl.verify();
							dsControl.reset();
							ds.getConnection();
							dsControl.setReturnValue(con, 1);
							dsControl.replay();
						}
						conControl.verify();
						conControl.reset();
						con.isReadOnly();
						conControl.setReturnValue(true, 1);
						con.close();
						conControl.setVoidCallable(1);
						conControl.replay();

						if (!openOuterConnection) {
							c = DataSourceUtils.getConnection(dsToUse);
						}
						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
						c.isReadOnly();
						DataSourceUtils.releaseConnection(c, dsToUse);

						c = DataSourceUtils.getConnection(dsToUse);
						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
						DataSourceUtils.releaseConnection(c, dsToUse);
					}
					catch (SQLException ex) {
					}
				}

				else {
					if (openOuterConnection) {
						try {
							conControl.verify();
							conControl.reset();
							con.close();
							conControl.setVoidCallable(1);
							conControl.replay();
						}
						catch (SQLException ex) {
						}
						DataSourceUtils.releaseConnection(c, dsToUse);
					}
				}
			}
		});

		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
		assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
		dsControl.verify();
		conControl.verify();
		utControl.verify();
		tmControl.verify();
	}

	public void testJtaTransactionCommitWithPropagationRequiredWithinSupports() throws Exception {
		doTestJtaTransactionCommitWithNewTransactionWithinEmptyTransaction(false, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiredWithinNotSupported() throws Exception {
		doTestJtaTransactionCommitWithNewTransactionWithinEmptyTransaction(false, true);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithinSupports() throws Exception {
		doTestJtaTransactionCommitWithNewTransactionWithinEmptyTransaction(true, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithinNotSupported() throws Exception {
		doTestJtaTransactionCommitWithNewTransactionWithinEmptyTransaction(true, true);
	}

	private void doTestJtaTransactionCommitWithNewTransactionWithinEmptyTransaction(
			final boolean requiresNew, boolean notSupported) throws Exception {

		MockControl utControl = MockControl.createControl(UserTransaction.class);
		UserTransaction ut = (UserTransaction) utControl.getMock();
		MockControl tmControl = MockControl.createControl(TransactionManager.class);
		TransactionManager tm = (TransactionManager) tmControl.getMock();
		MockControl txControl = MockControl.createControl(Transaction.class);
		Transaction tx = (Transaction) txControl.getMock();
		if (notSupported) {
			ut.getStatus();
			utControl.setReturnValue(Status.STATUS_ACTIVE, 1);
			tm.suspend();
			tmControl.setReturnValue(tx, 1);
		}
		else {
			ut.getStatus();
			utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		}
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		ut.begin();
		utControl.setVoidCallable(1);
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_ACTIVE, 2);
		ut.commit();
		utControl.setVoidCallable(1);
		if (notSupported) {
			tm.resume(tx);
			tmControl.setVoidCallable(1);
		}
		utControl.replay();
		tmControl.replay();
		txControl.replay();

		final MockControl dsControl = MockControl.createControl(DataSource.class);
		final DataSource ds = (DataSource) dsControl.getMock();
		final MockControl con1Control = MockControl.createControl(Connection.class);
		final Connection con1 = (Connection) con1Control.getMock();
		final MockControl con2Control = MockControl.createControl(Connection.class);
		final Connection con2 = (Connection) con2Control.getMock();
		ds.getConnection();
		dsControl.setReturnValue(con1, 1);
		ds.getConnection();
		dsControl.setReturnValue(con2, 1);
		con2.close();
		con2Control.setVoidCallable(1);
		con1.close();
		con1Control.setVoidCallable(1);
		dsControl.replay();
		con1Control.replay();
		con2Control.replay();

		final JtaTransactionManager ptm = new JtaTransactionManager(ut, tm);
		TransactionTemplate tt = new TransactionTemplate(ptm);
		tt.setPropagationBehavior(notSupported ?
				TransactionDefinition.PROPAGATION_NOT_SUPPORTED : TransactionDefinition.PROPAGATION_SUPPORTS);

		assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) {
				assertTrue(TransactionSynchronizationManager.isSynchronizationActive());
				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
				assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
				assertSame(con1, DataSourceUtils.getConnection(ds));
				assertSame(con1, DataSourceUtils.getConnection(ds));

				TransactionTemplate tt2 = new TransactionTemplate(ptm);
				tt2.setPropagationBehavior(requiresNew ?
						TransactionDefinition.PROPAGATION_REQUIRES_NEW : TransactionDefinition.PROPAGATION_REQUIRED);
				tt2.execute(new TransactionCallbackWithoutResult() {
					protected void doInTransactionWithoutResult(TransactionStatus status) {
						assertTrue(TransactionSynchronizationManager.isSynchronizationActive());
						assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
						assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
						assertSame(con2, DataSourceUtils.getConnection(ds));
						assertSame(con2, DataSourceUtils.getConnection(ds));
					}
				});

				assertTrue(TransactionSynchronizationManager.isSynchronizationActive());
				assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
				assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
				assertSame(con1, DataSourceUtils.getConnection(ds));
			}
		});
		assertFalse(TransactionSynchronizationManager.isSynchronizationActive());

		utControl.verify();
		tmControl.verify();
		txControl.verify();
		dsControl.verify();
		con1Control.verify();
		con2Control.verify();
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewAndSuspendException() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNewAndBeginException(true, false, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithOpenOuterConnectionAndSuspendException() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNewAndBeginException(true, true, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithTransactionAwareDataSourceAndSuspendException() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNewAndBeginException(true, false, true);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithOpenOuterConnectionAndTransactionAwareDataSourceAndSuspendException() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNewAndBeginException(true, true, true);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewAndBeginException() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNewAndBeginException(false, false, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithOpenOuterConnectionAndBeginException() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNewAndBeginException(false, true, false);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithOpenOuterConnectionAndTransactionAwareDataSourceAndBeginException() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNewAndBeginException(false, true, true);
	}

	public void testJtaTransactionCommitWithPropagationRequiresNewWithTransactionAwareDataSourceAndBeginException() throws Exception {
		doTestJtaTransactionWithPropagationRequiresNewAndBeginException(false, false, true);
	}

	private void doTestJtaTransactionWithPropagationRequiresNewAndBeginException(boolean suspendException,
			final boolean openOuterConnection, final boolean useTransactionAwareDataSource) throws Exception {

		MockControl utControl = MockControl.createControl(UserTransaction.class);
		UserTransaction ut = (UserTransaction) utControl.getMock();
		MockControl tmControl = MockControl.createControl(TransactionManager.class);
		TransactionManager tm = (TransactionManager) tmControl.getMock();
		MockControl txControl = MockControl.createControl(Transaction.class);
		Transaction tx = (Transaction) txControl.getMock();
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		ut.begin();
		utControl.setVoidCallable(1);
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_ACTIVE, 2);
		if (suspendException) {
			tm.suspend();
			tmControl.setThrowable(new SystemException(), 1);
		}
		else {
			tm.suspend();
			tmControl.setReturnValue(tx, 1);
			ut.begin();
			utControl.setThrowable(new SystemException(), 1);
			tm.resume(tx);
			tmControl.setVoidCallable(1);
		}
		ut.rollback();
		utControl.setVoidCallable(1);
		utControl.replay();
		tmControl.replay();

		final MockControl dsControl = MockControl.createControl(DataSource.class);
		final DataSource ds = (DataSource) dsControl.getMock();
		final MockControl conControl = MockControl.createControl(Connection.class);
		final Connection con = (Connection) conControl.getMock();
		ds.getConnection();
		dsControl.setReturnValue(con, 1);
		con.isReadOnly();
		conControl.setReturnValue(true, 1);
		if (!openOuterConnection || useTransactionAwareDataSource) {
			con.close();
			conControl.setVoidCallable(1);
		}
		conControl.replay();
		dsControl.replay();

		final DataSource dsToUse = useTransactionAwareDataSource ?
				new TransactionAwareDataSourceProxy(ds) : ds;
		if (dsToUse instanceof TransactionAwareDataSourceProxy) {
			((TransactionAwareDataSourceProxy) dsToUse).setReobtainTransactionalConnections(true);
		}

		JtaTransactionManager ptm = new JtaTransactionManager(ut, tm);
		final TransactionTemplate tt = new TransactionTemplate(ptm);
		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
		assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

		try {
			tt.execute(new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
					assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
					assertTrue("JTA synchronizations active", TransactionSynchronizationManager.isSynchronizationActive());
					assertTrue("Is new transaction", status.isNewTransaction());

					Connection c = DataSourceUtils.getConnection(dsToUse);
					try {
						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
						c.isReadOnly();
						DataSourceUtils.releaseConnection(c, dsToUse);

						c = DataSourceUtils.getConnection(dsToUse);
						assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
						if (!openOuterConnection) {
							DataSourceUtils.releaseConnection(c, dsToUse);
						}
					}
					catch (SQLException ex) {
					}

					try {
						tt.execute(new TransactionCallbackWithoutResult() {
							protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
								assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
								assertTrue("JTA synchronizations active", TransactionSynchronizationManager.isSynchronizationActive());
								assertTrue("Is new transaction", status.isNewTransaction());

								try {
									dsControl.verify();
									conControl.verify();
									dsControl.reset();
									conControl.reset();
									ds.getConnection();
									dsControl.setReturnValue(con, 1);
									con.close();
									conControl.setVoidCallable(1);
									dsControl.replay();
									conControl.replay();

									Connection c = DataSourceUtils.getConnection(dsToUse);
									assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
									DataSourceUtils.releaseConnection(c, dsToUse);

									c = DataSourceUtils.getConnection(dsToUse);
									assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
									DataSourceUtils.releaseConnection(c, dsToUse);
								}
								catch (SQLException ex) {
								}
							}
						});
					}
					finally {
						if (openOuterConnection) {
							try {
								dsControl.verify();
								dsControl.reset();
								conControl.verify();
								conControl.reset();

								if (useTransactionAwareDataSource) {
									ds.getConnection();
									dsControl.setReturnValue(con, 1);
								}
								con.isReadOnly();
								conControl.setReturnValue(true, 1);
								con.close();
								conControl.setVoidCallable(1);
								dsControl.replay();
								conControl.replay();

								c.isReadOnly();
								DataSourceUtils.releaseConnection(c, dsToUse);
							}
							catch (SQLException ex) {
							}
						}
					}
				}
			});

			fail("Should have thrown TransactionException");
		}
		catch (TransactionException ex) {
			// expected
		}

		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(dsToUse));
		assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());
		dsControl.verify();
		conControl.verify();
		utControl.verify();
		tmControl.verify();
	}

	public void testJtaTransactionWithConnectionHolderStillBound() throws Exception {
		MockControl utControl = MockControl.createControl(UserTransaction.class);
		UserTransaction ut = (UserTransaction) utControl.getMock();

		MockControl dsControl = MockControl.createControl(DataSource.class);
		final DataSource ds = (DataSource) dsControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();

		JtaTransactionManager ptm = new JtaTransactionManager(ut) {
			protected void doRegisterAfterCompletionWithJtaTransaction(
					JtaTransactionObject txObject, final List synchronizations) {
				Thread async = new Thread() {
					public void run() {
						invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_COMMITTED);
					}
				};
				async.start();
				try {
					async.join();
				}
				catch (InterruptedException ex) {
					ex.printStackTrace();
				}
			}
		};
		TransactionTemplate tt = new TransactionTemplate(ptm);
		assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
		assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

		for (int i = 0; i < 3; i++) {
			utControl.reset();
			ut.getStatus();
			utControl.setReturnValue(Status.STATUS_ACTIVE, 1);
			utControl.replay();

			dsControl.reset();
			conControl.reset();
			ds.getConnection();
			dsControl.setReturnValue(con, 1);
			con.close();
			conControl.setVoidCallable(1);
			dsControl.replay();
			conControl.replay();

			final boolean releaseCon = (i != 1);

			tt.execute(new TransactionCallbackWithoutResult() {
				protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
					assertTrue("JTA synchronizations active", TransactionSynchronizationManager.isSynchronizationActive());
					assertTrue("Is existing transaction", !status.isNewTransaction());

					Connection c = DataSourceUtils.getConnection(ds);
					assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
					DataSourceUtils.releaseConnection(c, ds);

					c = DataSourceUtils.getConnection(ds);
					assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(ds));
					if (releaseCon) {
						DataSourceUtils.releaseConnection(c, ds);
					}
				}
			});

			if (!releaseCon) {
				assertTrue("Still has connection holder", TransactionSynchronizationManager.hasResource(ds));
			}
			else {
				assertTrue("Hasn't thread connection", !TransactionSynchronizationManager.hasResource(ds));
			}
			assertTrue("JTA synchronizations not active", !TransactionSynchronizationManager.isSynchronizationActive());

			conControl.verify();
			dsControl.verify();
			utControl.verify();
		}
	}

	public void testJtaTransactionWithIsolationLevelDataSourceAdapter() throws Exception {
		MockControl utControl = MockControl.createControl(UserTransaction.class);
		UserTransaction ut = (UserTransaction) utControl.getMock();
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_ACTIVE, 2);
		ut.begin();
		utControl.setVoidCallable(1);
		ut.commit();
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_ACTIVE, 2);
		ut.begin();
		utControl.setVoidCallable(1);
		ut.commit();
		utControl.setVoidCallable(1);
		utControl.replay();

		MockControl ds1Control = MockControl.createControl(DataSource.class);
		final DataSource ds1 = (DataSource) ds1Control.getMock();
		MockControl con1Control = MockControl.createControl(Connection.class);
		final Connection con1 = (Connection) con1Control.getMock();
		ds1.getConnection();
		ds1Control.setReturnValue(con1, 1);
		con1.close();
		con1Control.setVoidCallable(1);
		ds1.getConnection();
		ds1Control.setReturnValue(con1, 1);
		con1.setReadOnly(true);
		con1Control.setVoidCallable(1);
		con1.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
		con1Control.setVoidCallable(1);
		con1.close();
		con1Control.setVoidCallable(1);
		con1Control.replay();
		ds1Control.replay();

		final IsolationLevelDataSourceAdapter dsToUse = new IsolationLevelDataSourceAdapter();
		dsToUse.setTargetDataSource(ds1);
		dsToUse.afterPropertiesSet();

		JtaTransactionManager ptm = new JtaTransactionManager(ut);
		ptm.setAllowCustomIsolationLevels(true);

		TransactionTemplate tt = new TransactionTemplate(ptm);
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
				Connection c = DataSourceUtils.getConnection(dsToUse);
				assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
				assertSame(con1, c);
				DataSourceUtils.releaseConnection(c, dsToUse);
			}
		});

		tt.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
		tt.setReadOnly(true);
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
				Connection c = DataSourceUtils.getConnection(dsToUse);
				assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
				assertSame(con1, c);
				DataSourceUtils.releaseConnection(c, dsToUse);
			}
		});

		ds1Control.verify();
		con1Control.verify();
		utControl.verify();
	}

	public void testJtaTransactionWithIsolationLevelDataSourceRouter() throws Exception {
		doTestJtaTransactionWithIsolationLevelDataSourceRouter(false);
	}

	public void testJtaTransactionWithIsolationLevelDataSourceRouterWithDataSourceLookup() throws Exception {
		doTestJtaTransactionWithIsolationLevelDataSourceRouter(true);
	}

	private void doTestJtaTransactionWithIsolationLevelDataSourceRouter(boolean dataSourceLookup) throws Exception {
		MockControl utControl = MockControl.createControl(UserTransaction.class);
		UserTransaction ut = (UserTransaction) utControl.getMock();
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_ACTIVE, 2);
		ut.begin();
		utControl.setVoidCallable(1);
		ut.commit();
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_NO_TRANSACTION, 1);
		ut.getStatus();
		utControl.setReturnValue(Status.STATUS_ACTIVE, 2);
		ut.begin();
		utControl.setVoidCallable(1);
		ut.commit();
		utControl.setVoidCallable(1);
		utControl.replay();

		MockControl ds1Control = MockControl.createControl(DataSource.class);
		final DataSource ds1 = (DataSource) ds1Control.getMock();
		MockControl con1Control = MockControl.createControl(Connection.class);
		final Connection con1 = (Connection) con1Control.getMock();
		ds1.getConnection();
		ds1Control.setReturnValue(con1, 1);
		con1.close();
		con1Control.setVoidCallable(1);
		con1Control.replay();
		ds1Control.replay();

		MockControl ds2Control = MockControl.createControl(DataSource.class);
		final DataSource ds2 = (DataSource) ds2Control.getMock();
		MockControl con2Control = MockControl.createControl(Connection.class);
		final Connection con2 = (Connection) con2Control.getMock();
		ds2.getConnection();
		ds2Control.setReturnValue(con2, 1);
		con2.close();
		con2Control.setVoidCallable(1);
		con2Control.replay();
		ds2Control.replay();

		final IsolationLevelDataSourceRouter dsToUse = new IsolationLevelDataSourceRouter();
		Map targetDataSources = new HashMap();
		if (dataSourceLookup) {
			targetDataSources.put("ISOLATION_REPEATABLE_READ", "ds2");
			dsToUse.setDefaultTargetDataSource("ds1");
			StaticListableBeanFactory beanFactory = new StaticListableBeanFactory();
			beanFactory.addBean("ds1", ds1);
			beanFactory.addBean("ds2", ds2);
			dsToUse.setDataSourceLookup(new BeanFactoryDataSourceLookup(beanFactory));
		}
		else {
			targetDataSources.put("ISOLATION_REPEATABLE_READ", ds2);
			dsToUse.setDefaultTargetDataSource(ds1);
		}
		dsToUse.setTargetDataSources(targetDataSources);
		dsToUse.afterPropertiesSet();

		JtaTransactionManager ptm = new JtaTransactionManager(ut);
		ptm.setAllowCustomIsolationLevels(true);

		TransactionTemplate tt = new TransactionTemplate(ptm);
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
				Connection c = DataSourceUtils.getConnection(dsToUse);
				assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
				assertSame(con1, c);
				DataSourceUtils.releaseConnection(c, dsToUse);
			}
		});

		tt.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) throws RuntimeException {
				Connection c = DataSourceUtils.getConnection(dsToUse);
				assertTrue("Has thread connection", TransactionSynchronizationManager.hasResource(dsToUse));
				assertSame(con2, c);
				DataSourceUtils.releaseConnection(c, dsToUse);
			}
		});

		ds1Control.verify();
		con1Control.verify();
		ds2Control.verify();
		con2Control.verify();
		utControl.verify();
	}

	protected void tearDown() {
		assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
		assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
		assertNull(TransactionSynchronizationManager.getCurrentTransactionName());
		assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
		assertNull(TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
		assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
	}

}
