/*
 * 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.listener;

import java.util.HashSet;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;

import org.easymock.MockControl;
import org.easymock.internal.AlwaysMatcher;

import org.springframework.core.task.TaskExecutor;
import org.springframework.jms.StubQueue;
import org.springframework.test.AssertThrows;

/**
 * @author Rick Evans
 * @author Juergen Hoeller
 */
public class SimpleMessageListenerContainerTests extends AbstractMessageListenerContainerTests {

	private static final String DESTINATION_NAME = "foo";

	private static final String EXCEPTION_MESSAGE = "This.Is.It";

	private static final StubQueue QUEUE_DESTINATION = new StubQueue();


	private SimpleMessageListenerContainer container;


	protected void setUp() throws Exception {
		this.container = (SimpleMessageListenerContainer) getContainer();
	}

	protected AbstractMessageListenerContainer getContainer() {
		return new SimpleMessageListenerContainer();
	}


	public void testSessionTransactedModeReallyDoesDefaultToFalse() throws Exception {
		assertFalse("The [pubSubLocal] property of SimpleMessageListenerContainer " +
				"must default to false. Change this test (and the " +
				"attendant Javadoc) if you have changed the default.",
				container.isPubSubNoLocal());
	}

	public void testSettingConcurrentConsumersToZeroIsNotAllowed() throws Exception {
		new AssertThrows(IllegalArgumentException.class) {
			public void test() throws Exception {
				container.setConcurrentConsumers(0);
				container.afterPropertiesSet();
			}
		}.runTest();
	}

	public void testSettingConcurrentConsumersToANegativeValueIsNotAllowed() throws Exception {
		new AssertThrows(IllegalArgumentException.class) {
			public void test() throws Exception {
				container.setConcurrentConsumers(-198);
				container.afterPropertiesSet();
			}
		}.runTest();
	}

	public void testInitDoesNotStartTheConnectionIfAutoStartIsSetToFalse() throws Exception {
		MockControl mockMessageConsumer = MockControl.createControl(MessageConsumer.class);
		MessageConsumer messageConsumer = (MessageConsumer) mockMessageConsumer.getMock();
		messageConsumer.setMessageListener(null);
		// anon. inner class passed in, so just expect a call...
		mockMessageConsumer.setMatcher(new AlwaysMatcher());
		mockMessageConsumer.setVoidCallable();
		mockMessageConsumer.replay();

		MockControl mockSession = MockControl.createControl(Session.class);
		Session session = (Session) mockSession.getMock();
		// Queue gets created in order to create MessageConsumer for that Destination...
		session.createQueue(DESTINATION_NAME);
		mockSession.setReturnValue(QUEUE_DESTINATION);
		// and then the MessageConsumer gets created...
		session.createConsumer(QUEUE_DESTINATION, null); // no MessageSelector...
		mockSession.setReturnValue(messageConsumer);
		mockSession.replay();

		MockControl mockConnection = MockControl.createControl(Connection.class);
		Connection connection = (Connection) mockConnection.getMock();
		connection.setExceptionListener(this.container);
		mockConnection.setVoidCallable();
		// session gets created in order to register MessageListener...
		connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode());
		mockConnection.setReturnValue(session);
		mockConnection.replay();

		MockControl mockConnectionFactory = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory connectionFactory = (ConnectionFactory) mockConnectionFactory.getMock();
		connectionFactory.createConnection();
		mockConnectionFactory.setReturnValue(connection);
		mockConnectionFactory.replay();

		this.container.setConnectionFactory(connectionFactory);
		this.container.setDestinationName(DESTINATION_NAME);

		this.container.setMessageListener(new TestMessageListener());
		this.container.setAutoStartup(false);
		this.container.afterPropertiesSet();

		mockMessageConsumer.verify();
		mockSession.verify();
		mockConnection.verify();
		mockConnectionFactory.verify();
	}

	public void testInitStartsTheConnectionByDefault() throws Exception {
		MockControl mockMessageConsumer = MockControl.createControl(MessageConsumer.class);
		MessageConsumer messageConsumer = (MessageConsumer) mockMessageConsumer.getMock();
		messageConsumer.setMessageListener(null);
		// anon. inner class passed in, so just expect a call...
		mockMessageConsumer.setMatcher(new AlwaysMatcher());
		mockMessageConsumer.setVoidCallable();
		mockMessageConsumer.replay();

		MockControl mockSession = MockControl.createControl(Session.class);
		Session session = (Session) mockSession.getMock();
		// Queue gets created in order to create MessageConsumer for that Destination...
		session.createQueue(DESTINATION_NAME);
		mockSession.setReturnValue(QUEUE_DESTINATION);
		// and then the MessageConsumer gets created...
		session.createConsumer(QUEUE_DESTINATION, null); // no MessageSelector...
		mockSession.setReturnValue(messageConsumer);
		mockSession.replay();

		MockControl mockConnection = MockControl.createControl(Connection.class);
		Connection connection = (Connection) mockConnection.getMock();
		connection.setExceptionListener(this.container);
		mockConnection.setVoidCallable();
		// session gets created in order to register MessageListener...
		connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode());
		mockConnection.setReturnValue(session);
		// and the connection is start()ed after the listener is registered...
		connection.start();
		mockConnection.setVoidCallable();
		mockConnection.replay();

		MockControl mockConnectionFactory = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory connectionFactory = (ConnectionFactory) mockConnectionFactory.getMock();
		connectionFactory.createConnection();
		mockConnectionFactory.setReturnValue(connection);
		mockConnectionFactory.replay();

		this.container.setConnectionFactory(connectionFactory);
		this.container.setDestinationName(DESTINATION_NAME);

		this.container.setMessageListener(new TestMessageListener());
		this.container.afterPropertiesSet();

		mockMessageConsumer.verify();
		mockSession.verify();
		mockConnection.verify();
		mockConnectionFactory.verify();
	}

	public void testCorrectSessionExposedForSessionAwareMessageListenerInvocation() throws Exception {
		final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer();

		MockControl mockSession = MockControl.createControl(Session.class);
		final Session session = (Session) mockSession.getMock();
		// Queue gets created in order to create MessageConsumer for that Destination...
		session.createQueue(DESTINATION_NAME);
		mockSession.setReturnValue(QUEUE_DESTINATION);
		// and then the MessageConsumer gets created...
		session.createConsumer(QUEUE_DESTINATION, null); // no MessageSelector...
		mockSession.setReturnValue(messageConsumer);
		// an exception is thrown, so the rollback logic is being applied here...
		session.getTransacted();
		mockSession.setReturnValue(false);
		session.getAcknowledgeMode();
		mockSession.setReturnValue(Session.AUTO_ACKNOWLEDGE);
		mockSession.replay();

		MockControl mockConnection = MockControl.createControl(Connection.class);
		Connection connection = (Connection) mockConnection.getMock();
		connection.setExceptionListener(this.container);
		mockConnection.setVoidCallable();
		// session gets created in order to register MessageListener...
		connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode());
		mockConnection.setReturnValue(session);
		// and the connection is start()ed after the listener is registered...
		connection.start();
		mockConnection.setVoidCallable();
		mockConnection.replay();

		MockControl mockConnectionFactory = MockControl.createControl(ConnectionFactory.class);
		final ConnectionFactory connectionFactory = (ConnectionFactory) mockConnectionFactory.getMock();
		connectionFactory.createConnection();
		mockConnectionFactory.setReturnValue(connection);
		mockConnectionFactory.replay();

		final HashSet failure = new HashSet();

		this.container.setConnectionFactory(connectionFactory);
		this.container.setDestinationName(DESTINATION_NAME);
		this.container.setMessageListener(new SessionAwareMessageListener() {
			public void onMessage(Message message, Session sess) {
				try {
					// Check correct Session passed into SessionAwareMessageListener.
					assertSame(sess, session);
				}
				catch (Throwable ex) {
					failure.add("MessageListener execution failed: " + ex);
				}
			}
		});

		this.container.afterPropertiesSet();

		MockControl mockMessage = MockControl.createControl(Message.class);
		final Message message = (Message) mockMessage.getMock();
		mockMessage.replay();
		messageConsumer.sendMessage(message);

		if (!failure.isEmpty()) {
			fail(failure.iterator().next().toString());
		}

		mockMessage.verify();
		mockSession.verify();
		mockConnection.verify();
		mockConnectionFactory.verify();
	}

	public void testTaskExecutorCorrectlyInvokedWhenSpecified() throws Exception {
		final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer();

		MockControl mockSession = MockControl.createControl(Session.class);
		final Session session = (Session) mockSession.getMock();
		session.createQueue(DESTINATION_NAME);
		mockSession.setReturnValue(QUEUE_DESTINATION);
		session.createConsumer(QUEUE_DESTINATION, null); // no MessageSelector...
		mockSession.setReturnValue(messageConsumer);
		session.getTransacted();
		mockSession.setReturnValue(false);
		session.getAcknowledgeMode();
		mockSession.setReturnValue(Session.AUTO_ACKNOWLEDGE);
		mockSession.replay();

		MockControl mockConnection = MockControl.createControl(Connection.class);
		Connection connection = (Connection) mockConnection.getMock();
		connection.setExceptionListener(this.container);
		mockConnection.setVoidCallable();
		connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode());
		mockConnection.setReturnValue(session);
		connection.start();
		mockConnection.setVoidCallable();
		mockConnection.replay();

		MockControl mockConnectionFactory = MockControl.createControl(ConnectionFactory.class);
		final ConnectionFactory connectionFactory = (ConnectionFactory) mockConnectionFactory.getMock();
		connectionFactory.createConnection();
		mockConnectionFactory.setReturnValue(connection);
		mockConnectionFactory.replay();

		final TestMessageListener listener = new TestMessageListener();

		this.container.setConnectionFactory(connectionFactory);
		this.container.setDestinationName(DESTINATION_NAME);
		this.container.setMessageListener(listener);
		this.container.setTaskExecutor(new TaskExecutor() {
			public void execute(Runnable task) {
				listener.executorInvoked = true;
				assertFalse(listener.listenerInvoked);
				task.run();
				assertTrue(listener.listenerInvoked);
			}
		});
		this.container.afterPropertiesSet();

		MockControl mockMessage = MockControl.createControl(Message.class);
		final Message message = (Message) mockMessage.getMock();
		mockMessage.replay();
		messageConsumer.sendMessage(message);

		assertTrue(listener.executorInvoked);
		assertTrue(listener.listenerInvoked);
		mockMessage.verify();
		mockSession.verify();
		mockConnection.verify();
		mockConnectionFactory.verify();
	}

	public void testRegisteredExceptionListenerIsInvokedOnException() throws Exception {
		final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer();

		MockControl mockSession = MockControl.createControl(Session.class);
		Session session = (Session) mockSession.getMock();
		// Queue gets created in order to create MessageConsumer for that Destination...
		session.createQueue(DESTINATION_NAME);
		mockSession.setReturnValue(QUEUE_DESTINATION);
		// and then the MessageConsumer gets created...
		session.createConsumer(QUEUE_DESTINATION, null); // no MessageSelector...
		mockSession.setReturnValue(messageConsumer);
		// an exception is thrown, so the rollback logic is being applied here...
		session.getTransacted();
		mockSession.setReturnValue(false);
		mockSession.replay();

		MockControl mockConnection = MockControl.createControl(Connection.class);
		Connection connection = (Connection) mockConnection.getMock();
		connection.setExceptionListener(this.container);
		mockConnection.setVoidCallable();
		// session gets created in order to register MessageListener...
		connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode());
		mockConnection.setReturnValue(session);
		// and the connection is start()ed after the listener is registered...
		connection.start();
		mockConnection.setVoidCallable();
		mockConnection.replay();

		MockControl mockConnectionFactory = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory connectionFactory = (ConnectionFactory) mockConnectionFactory.getMock();
		connectionFactory.createConnection();
		mockConnectionFactory.setReturnValue(connection);
		mockConnectionFactory.replay();

		final JMSException theException = new JMSException(EXCEPTION_MESSAGE);

		this.container.setConnectionFactory(connectionFactory);
		this.container.setDestinationName(DESTINATION_NAME);
		this.container.setMessageListener(new SessionAwareMessageListener() {
			public void onMessage(Message message, Session session) throws JMSException {
				throw theException;
			}
		});

		MockControl mockExceptionListener = MockControl.createControl(ExceptionListener.class);
		ExceptionListener exceptionListener = (ExceptionListener) mockExceptionListener.getMock();
		exceptionListener.onException(theException);
		mockExceptionListener.setVoidCallable();
		mockExceptionListener.replay();

		this.container.setExceptionListener(exceptionListener);
		this.container.afterPropertiesSet();

		// manually trigger an Exception with the above bad MessageListener...
		MockControl mockMessage = MockControl.createControl(Message.class);
		final Message message = (Message) mockMessage.getMock();
		mockMessage.replay();

		// a Throwable from a MessageListener MUST simply be swallowed...
		messageConsumer.sendMessage(message);

		mockExceptionListener.verify();
		mockMessage.verify();
		mockSession.verify();
		mockConnection.verify();
		mockConnectionFactory.verify();
	}

	public void testNoRollbackOccursIfSessionIsNotTransactedAndThatExceptionsDo_NOT_Propagate() throws Exception {
		final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer();

		MockControl mockSession = MockControl.createControl(Session.class);
		Session session = (Session) mockSession.getMock();
		// Queue gets created in order to create MessageConsumer for that Destination...
		session.createQueue(DESTINATION_NAME);
		mockSession.setReturnValue(QUEUE_DESTINATION);
		// and then the MessageConsumer gets created...
		session.createConsumer(QUEUE_DESTINATION, null); // no MessageSelector...
		mockSession.setReturnValue(messageConsumer);
		// an exception is thrown, so the rollback logic is being applied here...
		session.getTransacted();
		mockSession.setReturnValue(false);
		mockSession.replay();

		MockControl mockConnection = MockControl.createControl(Connection.class);
		Connection connection = (Connection) mockConnection.getMock();
		connection.setExceptionListener(this.container);
		mockConnection.setVoidCallable();
		// session gets created in order to register MessageListener...
		connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode());
		mockConnection.setReturnValue(session);
		// and the connection is start()ed after the listener is registered...
		connection.start();
		mockConnection.setVoidCallable();
		mockConnection.replay();

		MockControl mockConnectionFactory = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory connectionFactory = (ConnectionFactory) mockConnectionFactory.getMock();
		connectionFactory.createConnection();
		mockConnectionFactory.setReturnValue(connection);
		mockConnectionFactory.replay();

		this.container.setConnectionFactory(connectionFactory);
		this.container.setDestinationName(DESTINATION_NAME);
		this.container.setMessageListener(new MessageListener() {
			public void onMessage(Message message) {
				throw new UnsupportedOperationException();
			}
		});
		this.container.afterPropertiesSet();

		// manually trigger an Exception with the above bad MessageListener...
		MockControl mockMessage = MockControl.createControl(Message.class);
		final Message message = (Message) mockMessage.getMock();
		mockMessage.replay();

		// a Throwable from a MessageListener MUST simply be swallowed...
		messageConsumer.sendMessage(message);

		mockMessage.verify();
		mockSession.verify();
		mockConnection.verify();
		mockConnectionFactory.verify();
	}

	public void testTransactedSessionsGetRollbackLogicAppliedAndThatExceptionsStillDo_NOT_Propagate() throws Exception {
		this.container.setSessionTransacted(true);

		final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer();

		MockControl mockSession = MockControl.createControl(Session.class);
		Session session = (Session) mockSession.getMock();
		// Queue gets created in order to create MessageConsumer for that Destination...
		session.createQueue(DESTINATION_NAME);
		mockSession.setReturnValue(QUEUE_DESTINATION);
		// and then the MessageConsumer gets created...
		session.createConsumer(QUEUE_DESTINATION, null); // no MessageSelector...
		mockSession.setReturnValue(messageConsumer);
		// an exception is thrown, so the rollback logic is being applied here...
		session.getTransacted();
		mockSession.setReturnValue(true);
		// Session is rolled back 'cos it is transacted...
		session.rollback();
		mockSession.setVoidCallable();
		mockSession.replay();

		MockControl mockConnection = MockControl.createControl(Connection.class);
		Connection connection = (Connection) mockConnection.getMock();
		connection.setExceptionListener(this.container);
		mockConnection.setVoidCallable();
		// session gets created in order to register MessageListener...
		connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode());
		mockConnection.setReturnValue(session);
		// and the connection is start()ed after the listener is registered...
		connection.start();
		mockConnection.setVoidCallable();
		mockConnection.replay();

		MockControl mockConnectionFactory = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory connectionFactory = (ConnectionFactory) mockConnectionFactory.getMock();
		connectionFactory.createConnection();
		mockConnectionFactory.setReturnValue(connection);
		mockConnectionFactory.replay();

		this.container.setConnectionFactory(connectionFactory);
		this.container.setDestinationName(DESTINATION_NAME);
		this.container.setMessageListener(new MessageListener() {
			public void onMessage(Message message) {
				throw new UnsupportedOperationException();
			}
		});
		this.container.afterPropertiesSet();

		// manually trigger an Exception with the above bad MessageListener...
		MockControl mockMessage = MockControl.createControl(Message.class);
		final Message message = (Message) mockMessage.getMock();
		mockMessage.replay();

		// a Throwable from a MessageListener MUST simply be swallowed...
		messageConsumer.sendMessage(message);

		mockMessage.verify();
		mockSession.verify();
		mockConnection.verify();
		mockConnectionFactory.verify();
	}

	public void testDestroyClosesConsumersSessionsAndConnectionInThatOrder() throws Exception {
		MockControl mockMessageConsumer = MockControl.createControl(MessageConsumer.class);
		MessageConsumer messageConsumer = (MessageConsumer) mockMessageConsumer.getMock();
		messageConsumer.setMessageListener(null);
		// anon. inner class passed in, so just expect a call...
		mockMessageConsumer.setMatcher(new AlwaysMatcher());
		mockMessageConsumer.setVoidCallable();
		// closing down...
		messageConsumer.close();
		mockMessageConsumer.setVoidCallable();
		mockMessageConsumer.replay();

		MockControl mockSession = MockControl.createControl(Session.class);
		Session session = (Session) mockSession.getMock();
		// Queue gets created in order to create MessageConsumer for that Destination...
		session.createQueue(DESTINATION_NAME);
		mockSession.setReturnValue(QUEUE_DESTINATION);
		// and then the MessageConsumer gets created...
		session.createConsumer(QUEUE_DESTINATION, null); // no MessageSelector...
		mockSession.setReturnValue(messageConsumer);
		// closing down...
		session.close();
		mockSession.setVoidCallable();
		mockSession.replay();

		MockControl mockConnection = MockControl.createControl(Connection.class);
		Connection connection = (Connection) mockConnection.getMock();
		connection.setExceptionListener(this.container);
		mockConnection.setVoidCallable();
		// session gets created in order to register MessageListener...
		connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode());
		mockConnection.setReturnValue(session);
		// and the connection is start()ed after the listener is registered...
		connection.start();
		mockConnection.setVoidCallable();
		// closing down...
		connection.close();
		mockConnection.setVoidCallable();
		mockConnection.replay();

		MockControl mockConnectionFactory = MockControl.createControl(ConnectionFactory.class);
		ConnectionFactory connectionFactory = (ConnectionFactory) mockConnectionFactory.getMock();
		connectionFactory.createConnection();
		mockConnectionFactory.setReturnValue(connection);
		mockConnectionFactory.replay();

		this.container.setConnectionFactory(connectionFactory);
		this.container.setDestinationName(DESTINATION_NAME);

		this.container.setMessageListener(new TestMessageListener());
		this.container.afterPropertiesSet();
		this.container.destroy();

		mockMessageConsumer.verify();
		mockSession.verify();
		mockConnection.verify();
		mockConnectionFactory.verify();
	}


	private static class TestMessageListener implements MessageListener {

		public boolean executorInvoked = false;

		public boolean listenerInvoked = false;

		public void onMessage(Message message) {
			this.listenerInvoked = true;
		}
	}


	private static class SimpleMessageConsumer implements MessageConsumer {

		private MessageListener messageListener;

		public void sendMessage(Message message) throws JMSException {
			this.messageListener.onMessage(message);
		}

		public String getMessageSelector() throws JMSException {
			throw new UnsupportedOperationException();
		}

		public MessageListener getMessageListener() throws JMSException {
			return this.messageListener;
		}

		public void setMessageListener(MessageListener messageListener) throws JMSException {
			this.messageListener = messageListener;
		}

		public Message receive() throws JMSException {
			throw new UnsupportedOperationException();
		}

		public Message receive(long l) throws JMSException {
			throw new UnsupportedOperationException();
		}

		public Message receiveNoWait() throws JMSException {
			throw new UnsupportedOperationException();
		}

		public void close() throws JMSException {
			throw new UnsupportedOperationException();
		}
	}

}
