/*
 * Copyright 2002-2008 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.jms.connection;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;

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

import org.springframework.jms.StubQueue;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.JmsTemplate102;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.SessionCallback;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * @author Juergen Hoeller
 * @since 26.07.2004
 */
public class JmsTransactionManagerTests extends TestCase {

	public void testTransactionCommit() throws JMSException {
		MockControl cfControl = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory cf = (ConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(Session.class);
		final Session session = (Session) sessionControl.getMock();

		cf.createConnection();
		cfControl.setReturnValue(con, 1);
		con.createSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		session.commit();
		sessionControl.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(1);

		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager(cf);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		JmsTemplate jt = new JmsTemplate(cf);
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		tm.commit(ts);

		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	public void testTransactionRollback() throws JMSException {
		MockControl cfControl = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory cf = (ConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(Session.class);
		final Session session = (Session) sessionControl.getMock();

		cf.createConnection();
		cfControl.setReturnValue(con, 1);
		con.createSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		session.rollback();
		sessionControl.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(1);

		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager(cf);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		JmsTemplate jt = new JmsTemplate(cf);
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		tm.rollback(ts);

		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	public void testParticipatingTransactionWithCommit() throws JMSException {
		MockControl cfControl = MockControl.createControl(ConnectionFactory.class);
		final ConnectionFactory cf = (ConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(Session.class);
		final Session session = (Session) sessionControl.getMock();

		cf.createConnection();
		cfControl.setReturnValue(con, 1);
		con.createSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		session.commit();
		sessionControl.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(1);

		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager(cf);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		final JmsTemplate jt = new JmsTemplate(cf);
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		TransactionTemplate tt = new TransactionTemplate(tm);
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) {
				jt.execute(new SessionCallback() {
					public Object doInJms(Session sess) {
						assertTrue(sess == session);
						return null;
					}
				});
			}
		});
		tm.commit(ts);

		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	public void testParticipatingTransactionWithRollbackOnly() throws JMSException {
		MockControl cfControl = MockControl.createControl(ConnectionFactory.class);
		final ConnectionFactory cf = (ConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(Session.class);
		final Session session = (Session) sessionControl.getMock();

		cf.createConnection();
		cfControl.setReturnValue(con, 1);
		con.createSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		session.rollback();
		sessionControl.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(1);

		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager(cf);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		final JmsTemplate jt = new JmsTemplate(cf);
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		TransactionTemplate tt = new TransactionTemplate(tm);
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) {
				jt.execute(new SessionCallback() {
					public Object doInJms(Session sess) {
						assertTrue(sess == session);
						return null;
					}
				});
				status.setRollbackOnly();
			}
		});
		try {
			tm.commit(ts);
			fail("Should have thrown UnexpectedRollbackException");
		}
		catch (UnexpectedRollbackException ex) {
			// expected
		}

		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	public void testSuspendedTransaction() throws JMSException {
		MockControl cfControl = MockControl.createControl(ConnectionFactory.class);
		final ConnectionFactory cf = (ConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(Session.class);
		final Session session = (Session) sessionControl.getMock();
		MockControl session2Control = MockControl.createControl(Session.class);
		final Session session2 = (Session) session2Control.getMock();

		cf.createConnection();
		cfControl.setReturnValue(con, 2);
		con.createSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		con.createSession(false, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session2, 1);
		session.commit();
		sessionControl.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		session2.close();
		session2Control.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(2);

		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager(cf);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		final JmsTemplate jt = new JmsTemplate(cf);
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		TransactionTemplate tt = new TransactionTemplate(tm);
		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) {
				jt.execute(new SessionCallback() {
					public Object doInJms(Session sess) {
						assertTrue(sess != session);
						return null;
					}
				});
			}
		});
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		tm.commit(ts);

		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	public void testTransactionSuspension() throws JMSException {
		MockControl cfControl = MockControl.createControl(ConnectionFactory.class);
		final ConnectionFactory cf = (ConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(Session.class);
		final Session session = (Session) sessionControl.getMock();
		MockControl session2Control = MockControl.createControl(Session.class);
		final Session session2 = (Session) session2Control.getMock();

		cf.createConnection();
		cfControl.setReturnValue(con, 2);
		con.createSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		con.createSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session2, 1);
		session.commit();
		sessionControl.setVoidCallable(1);
		session2.commit();
		session2Control.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		session2.close();
		session2Control.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(2);

		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager(cf);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		final JmsTemplate jt = new JmsTemplate(cf);
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		TransactionTemplate tt = new TransactionTemplate(tm);
		tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
		tt.execute(new TransactionCallbackWithoutResult() {
			protected void doInTransactionWithoutResult(TransactionStatus status) {
				jt.execute(new SessionCallback() {
					public Object doInJms(Session sess) {
						assertTrue(sess != session);
						return null;
					}
				});
			}
		});
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		tm.commit(ts);

		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	public void testTransactionCommitWithMessageProducer() throws JMSException {
		Destination dest = new StubQueue();

		MockControl cfControl = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory cf = (ConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(Connection.class);
		Connection con = (Connection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(Session.class);
		Session session = (Session) sessionControl.getMock();
		MockControl producerControl = MockControl.createControl(MessageProducer.class);
		MessageProducer producer = (MessageProducer) producerControl.getMock();
		MockControl messageControl = MockControl.createControl(Message.class);
		final Message message = (Message) messageControl.getMock();

		cf.createConnection();
		cfControl.setReturnValue(con, 1);
		con.createSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		session.createProducer(dest);
		sessionControl.setReturnValue(producer, 1);
		producer.send(message);
		producerControl.setVoidCallable(1);
		session.getTransacted();
		sessionControl.setReturnValue(true, 1);
		session.commit();
		sessionControl.setVoidCallable(1);
		producer.close();
		producerControl.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(1);

		producerControl.replay();
		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager(cf);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		JmsTemplate jt = new JmsTemplate(cf);
		jt.send(dest, new MessageCreator() {
			public Message createMessage(Session session) throws JMSException {
				return message;
			}
		});
		tm.commit(ts);

		producerControl.verify();
		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	public void testTransactionCommit102WithQueue() throws JMSException {
		MockControl cfControl = MockControl.createControl(QueueConnectionFactory.class);
		QueueConnectionFactory cf = (QueueConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(QueueConnection.class);
		QueueConnection con = (QueueConnection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(QueueSession.class);
		final QueueSession session = (QueueSession) sessionControl.getMock();

		cf.createQueueConnection();
		cfControl.setReturnValue(con, 1);
		con.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		session.commit();
		sessionControl.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(1);

		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager102(cf, false);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		JmsTemplate jt = new JmsTemplate102(cf, false);
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		tm.commit(ts);

		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	public void testTransactionCommit102WithTopic() throws JMSException {
		MockControl cfControl = MockControl.createControl(TopicConnectionFactory.class);
		TopicConnectionFactory cf = (TopicConnectionFactory) cfControl.getMock();
		MockControl conControl = MockControl.createControl(TopicConnection.class);
		TopicConnection con = (TopicConnection) conControl.getMock();
		MockControl sessionControl = MockControl.createControl(TopicSession.class);
		final TopicSession session = (TopicSession) sessionControl.getMock();

		cf.createTopicConnection();
		cfControl.setReturnValue(con, 1);
		con.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
		conControl.setReturnValue(session, 1);
		session.commit();
		sessionControl.setVoidCallable(1);
		session.close();
		sessionControl.setVoidCallable(1);
		con.close();
		conControl.setVoidCallable(1);

		sessionControl.replay();
		conControl.replay();
		cfControl.replay();

		JmsTransactionManager tm = new JmsTransactionManager102(cf, true);
		TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
		JmsTemplate jt = new JmsTemplate102(cf, true);
		jt.execute(new SessionCallback() {
			public Object doInJms(Session sess) {
				assertTrue(sess == session);
				return null;
			}
		});
		tm.commit(ts);

		sessionControl.verify();
		conControl.verify();
		cfControl.verify();
	}

	protected void tearDown() {
		assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
		assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
	}

}
