/* * Copyright 2002-2016 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 java.util.Set; 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.junit.Before; import org.junit.Test; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.task.TaskExecutor; import org.springframework.jms.StubQueue; import org.springframework.util.ErrorHandler; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; /** * @author Rick Evans * @author Juergen Hoeller * @author Chris Beams * @author Mark Fisher */ 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; @Before public void setUp() throws Exception { this.container = (SimpleMessageListenerContainer) getContainer(); } @Override protected AbstractMessageListenerContainer getContainer() { return new SimpleMessageListenerContainer(); } @Test 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()); } @Test(expected = IllegalArgumentException.class) public void testSettingConcurrentConsumersToZeroIsNotAllowed() throws Exception { container.setConcurrentConsumers(0); container.afterPropertiesSet(); } @Test(expected = IllegalArgumentException.class) public void testSettingConcurrentConsumersToANegativeValueIsNotAllowed() throws Exception { container.setConcurrentConsumers(-198); container.afterPropertiesSet(); } @Test public void testContextRefreshedEventDoesNotStartTheConnectionIfAutoStartIsSetToFalse() throws Exception { MessageConsumer messageConsumer = mock(MessageConsumer.class); Session session = mock(Session.class); // Queue gets created in order to create MessageConsumer for that Destination... given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); // and then the MessageConsumer gets created... given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... Connection connection = mock(Connection.class); // session gets created in order to register MessageListener... given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(new TestMessageListener()); this.container.setAutoStartup(false); this.container.afterPropertiesSet(); GenericApplicationContext context = new GenericApplicationContext(); context.getBeanFactory().registerSingleton("messageListenerContainer", this.container); context.refresh(); verify(connection).setExceptionListener(this.container); } @Test public void testContextRefreshedEventStartsTheConnectionByDefault() throws Exception { MessageConsumer messageConsumer = mock(MessageConsumer.class); Session session = mock(Session.class); // Queue gets created in order to create MessageConsumer for that Destination... given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); // and then the MessageConsumer gets created... given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... Connection connection = mock(Connection.class); // session gets created in order to register MessageListener... given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); // and the connection is start()ed after the listener is registered... ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(new TestMessageListener()); this.container.afterPropertiesSet(); GenericApplicationContext context = new GenericApplicationContext(); context.getBeanFactory().registerSingleton("messageListenerContainer", this.container); context.refresh(); verify(connection).setExceptionListener(this.container); verify(connection).start(); } @Test public void testCorrectSessionExposedForSessionAwareMessageListenerInvocation() throws Exception { final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer(); final Session session = mock(Session.class); // Queue gets created in order to create MessageConsumer for that Destination... given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); // and then the MessageConsumer gets created... given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... // an exception is thrown, so the rollback logic is being applied here... given(session.getTransacted()).willReturn(false); given(session.getAcknowledgeMode()).willReturn(Session.AUTO_ACKNOWLEDGE); Connection connection = mock(Connection.class); // session gets created in order to register MessageListener... given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); // and the connection is start()ed after the listener is registered... final ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); final Set<String> failure = new HashSet<>(1); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(new SessionAwareMessageListener<Message>() { @Override 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(); this.container.start(); final Message message = mock(Message.class); messageConsumer.sendMessage(message); if (!failure.isEmpty()) { fail(failure.iterator().next().toString()); } verify(connection).setExceptionListener(this.container); verify(connection).start(); } @Test public void testTaskExecutorCorrectlyInvokedWhenSpecified() throws Exception { final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer(); final Session session = mock(Session.class); given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... given(session.getTransacted()).willReturn(false); given(session.getAcknowledgeMode()).willReturn(Session.AUTO_ACKNOWLEDGE); Connection connection = mock(Connection.class); given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); final ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); final TestMessageListener listener = new TestMessageListener(); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(listener); this.container.setTaskExecutor(new TaskExecutor() { @Override public void execute(Runnable task) { listener.executorInvoked = true; assertFalse(listener.listenerInvoked); task.run(); assertTrue(listener.listenerInvoked); } }); this.container.afterPropertiesSet(); this.container.start(); final Message message = mock(Message.class); messageConsumer.sendMessage(message); assertTrue(listener.executorInvoked); assertTrue(listener.listenerInvoked); verify(connection).setExceptionListener(this.container); verify(connection).start(); } @Test public void testRegisteredExceptionListenerIsInvokedOnException() throws Exception { final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer(); Session session = mock(Session.class); // Queue gets created in order to create MessageConsumer for that Destination... given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); // and then the MessageConsumer gets created... given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... // an exception is thrown, so the rollback logic is being applied here... given(session.getTransacted()).willReturn(false); Connection connection = mock(Connection.class); // session gets created in order to register MessageListener... given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); // and the connection is start()ed after the listener is registered... ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); final JMSException theException = new JMSException(EXCEPTION_MESSAGE); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(new SessionAwareMessageListener<Message>() { @Override public void onMessage(Message message, Session session) throws JMSException { throw theException; } }); ExceptionListener exceptionListener = mock(ExceptionListener.class); this.container.setExceptionListener(exceptionListener); this.container.afterPropertiesSet(); this.container.start(); // manually trigger an Exception with the above bad MessageListener... final Message message = mock(Message.class); // a Throwable from a MessageListener MUST simply be swallowed... messageConsumer.sendMessage(message); verify(connection).setExceptionListener(this.container); verify(connection).start(); verify(exceptionListener).onException(theException); } @Test public void testRegisteredErrorHandlerIsInvokedOnException() throws Exception { final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer(); Session session = mock(Session.class); // Queue gets created in order to create MessageConsumer for that Destination... given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); // and then the MessageConsumer gets created... given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... // an exception is thrown, so the rollback logic is being applied here... given(session.getTransacted()).willReturn(false); Connection connection = mock(Connection.class); // session gets created in order to register MessageListener... given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); final IllegalStateException theException = new IllegalStateException("intentional test failure"); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(new SessionAwareMessageListener<Message>() { @Override public void onMessage(Message message, Session session) throws JMSException { throw theException; } }); ErrorHandler errorHandler = mock(ErrorHandler.class); this.container.setErrorHandler(errorHandler); this.container.afterPropertiesSet(); this.container.start(); // manually trigger an Exception with the above bad MessageListener... Message message = mock(Message.class); // a Throwable from a MessageListener MUST simply be swallowed... messageConsumer.sendMessage(message); verify(connection).setExceptionListener(this.container); verify(connection).start(); verify(errorHandler).handleError(theException); } @Test public void testNoRollbackOccursIfSessionIsNotTransactedAndThatExceptionsDo_NOT_Propagate() throws Exception { final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer(); Session session = mock(Session.class); // Queue gets created in order to create MessageConsumer for that Destination... given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); // and then the MessageConsumer gets created... given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... // an exception is thrown, so the rollback logic is being applied here... given(session.getTransacted()).willReturn(false); Connection connection = mock(Connection.class); // session gets created in order to register MessageListener... given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); // and the connection is start()ed after the listener is registered... ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { throw new UnsupportedOperationException(); } }); this.container.afterPropertiesSet(); this.container.start(); // manually trigger an Exception with the above bad MessageListener... final Message message = mock(Message.class); // a Throwable from a MessageListener MUST simply be swallowed... messageConsumer.sendMessage(message); verify(connection).setExceptionListener(this.container); verify(connection).start(); } @Test public void testTransactedSessionsGetRollbackLogicAppliedAndThatExceptionsStillDo_NOT_Propagate() throws Exception { this.container.setSessionTransacted(true); final SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer(); Session session = mock(Session.class); // Queue gets created in order to create MessageConsumer for that Destination... given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); // and then the MessageConsumer gets created... given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... // an exception is thrown, so the rollback logic is being applied here... given(session.getTransacted()).willReturn(true); Connection connection = mock(Connection.class); // session gets created in order to register MessageListener... given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); // and the connection is start()ed after the listener is registered... ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { throw new UnsupportedOperationException(); } }); this.container.afterPropertiesSet(); this.container.start(); // manually trigger an Exception with the above bad MessageListener... final Message message = mock(Message.class); // a Throwable from a MessageListener MUST simply be swallowed... messageConsumer.sendMessage(message); // Session is rolled back 'cos it is transacted... verify(session).rollback(); verify(connection).setExceptionListener(this.container); verify(connection).start(); } @Test public void testDestroyClosesConsumersSessionsAndConnectionInThatOrder() throws Exception { MessageConsumer messageConsumer = mock(MessageConsumer.class); Session session = mock(Session.class); // Queue gets created in order to create MessageConsumer for that Destination... given(session.createQueue(DESTINATION_NAME)).willReturn(QUEUE_DESTINATION); // and then the MessageConsumer gets created... given(session.createConsumer(QUEUE_DESTINATION, null)).willReturn(messageConsumer); // no MessageSelector... Connection connection = mock(Connection.class); // session gets created in order to register MessageListener... given(connection.createSession(this.container.isSessionTransacted(), this.container.getSessionAcknowledgeMode())).willReturn(session); // and the connection is start()ed after the listener is registered... ConnectionFactory connectionFactory = mock(ConnectionFactory.class); given(connectionFactory.createConnection()).willReturn(connection); this.container.setConnectionFactory(connectionFactory); this.container.setDestinationName(DESTINATION_NAME); this.container.setMessageListener(new TestMessageListener()); this.container.afterPropertiesSet(); this.container.start(); this.container.destroy(); verify(messageConsumer).close(); verify(session).close(); verify(connection).setExceptionListener(this.container); verify(connection).start(); verify(connection).close(); } private static class TestMessageListener implements MessageListener { public boolean executorInvoked = false; public boolean listenerInvoked = false; @Override 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); } @Override public String getMessageSelector() throws JMSException { throw new UnsupportedOperationException(); } @Override public MessageListener getMessageListener() throws JMSException { return this.messageListener; } @Override public void setMessageListener(MessageListener messageListener) throws JMSException { this.messageListener = messageListener; } @Override public Message receive() throws JMSException { throw new UnsupportedOperationException(); } @Override public Message receive(long l) throws JMSException { throw new UnsupportedOperationException(); } @Override public Message receiveNoWait() throws JMSException { throw new UnsupportedOperationException(); } @Override public void close() throws JMSException { throw new UnsupportedOperationException(); } } }