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

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.jms.JMSException;
import javax.jms.MessageListener;

import org.hamcrest.core.Is;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AliasFor;
import org.springframework.jms.config.JmsListenerContainerTestFactory;
import org.springframework.jms.config.JmsListenerEndpointRegistrar;
import org.springframework.jms.config.JmsListenerEndpointRegistry;
import org.springframework.jms.config.MessageListenerTestContainer;
import org.springframework.jms.config.MethodJmsListenerEndpoint;
import org.springframework.jms.config.SimpleJmsListenerEndpoint;
import org.springframework.jms.listener.adapter.ListenerExecutionFailedException;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException;
import org.springframework.stereotype.Component;

import static org.junit.Assert.*;

/**
 * @author Stephane Nicoll
 * @author Sam Brannen
 */
public class EnableJmsTests extends AbstractJmsAnnotationDrivenTests {

	@Rule
	public final ExpectedException thrown = ExpectedException.none();


	@Override
	@Test
	public void sampleConfiguration() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsSampleConfig.class, SampleBean.class);
		testSampleConfiguration(context);
	}

	@Override
	@Test
	public void fullConfiguration() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsFullConfig.class, FullBean.class);
		testFullConfiguration(context);
	}

	@Override
	public void fullConfigurableConfiguration() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsFullConfigurableConfig.class, FullConfigurableBean.class);
		testFullConfiguration(context);
	}

	@Override
	@Test
	public void customConfiguration() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsCustomConfig.class, CustomBean.class);
		testCustomConfiguration(context);
	}

	@Override
	@Test
	public void explicitContainerFactory() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsCustomContainerFactoryConfig.class, DefaultBean.class);
		testExplicitContainerFactoryConfiguration(context);
	}

	@Override
	@Test
	public void defaultContainerFactory() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsDefaultContainerFactoryConfig.class, DefaultBean.class);
		testDefaultContainerFactoryConfiguration(context);
	}

	@Test
	public void containerAreStartedByDefault() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsDefaultContainerFactoryConfig.class, DefaultBean.class);
		JmsListenerContainerTestFactory factory =
				context.getBean(JmsListenerContainerTestFactory.class);
		MessageListenerTestContainer container = factory.getListenerContainers().get(0);
		assertTrue(container.isAutoStartup());
		assertTrue(container.isStarted());
	}

	@Test
	public void containerCanBeStarterViaTheRegistry() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsAutoStartupFalseConfig.class, DefaultBean.class);
		JmsListenerContainerTestFactory factory =
				context.getBean(JmsListenerContainerTestFactory.class);
		MessageListenerTestContainer container = factory.getListenerContainers().get(0);
		assertFalse(container.isAutoStartup());
		assertFalse(container.isStarted());
		JmsListenerEndpointRegistry registry = context.getBean(JmsListenerEndpointRegistry.class);
		registry.start();
		assertTrue(container.isStarted());
	}

	@Override
	@Test
	public void jmsHandlerMethodFactoryConfiguration() throws JMSException {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsHandlerMethodFactoryConfig.class, ValidationBean.class);

		thrown.expect(ListenerExecutionFailedException.class);
		thrown.expectCause(Is.<MethodArgumentNotValidException>isA(MethodArgumentNotValidException.class));
		testJmsHandlerMethodFactoryConfiguration(context);
	}

	@Override
	@Test
	public void jmsListenerIsRepeatable() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsDefaultContainerFactoryConfig.class, JmsListenerRepeatableBean.class);
		testJmsListenerRepeatable(context);
	}

	@Override
	@Test
	public void jmsListeners() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsDefaultContainerFactoryConfig.class, JmsListenersBean.class);
		testJmsListenerRepeatable(context);
	}

	@Test
	public void composedJmsListeners() {
		try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
			EnableJmsDefaultContainerFactoryConfig.class, ComposedJmsListenersBean.class)) {
			JmsListenerContainerTestFactory simpleFactory = context.getBean("jmsListenerContainerFactory",
				JmsListenerContainerTestFactory.class);
			assertEquals(2, simpleFactory.getListenerContainers().size());

			MethodJmsListenerEndpoint first = (MethodJmsListenerEndpoint) simpleFactory.getListenerContainer(
				"first").getEndpoint();
			assertEquals("first", first.getId());
			assertEquals("orderQueue", first.getDestination());
			assertNull(first.getConcurrency());

			MethodJmsListenerEndpoint second = (MethodJmsListenerEndpoint) simpleFactory.getListenerContainer(
				"second").getEndpoint();
			assertEquals("second", second.getId());
			assertEquals("billingQueue", second.getDestination());
			assertEquals("2-10", second.getConcurrency());
		}
	}

	@Test
	@SuppressWarnings("resource")
	public void unknownFactory() {
		thrown.expect(BeanCreationException.class);
		thrown.expectMessage("customFactory");  // not found
		new AnnotationConfigApplicationContext(EnableJmsSampleConfig.class, CustomBean.class);
	}

	@Test
	public void lazyComponent() {
		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
				EnableJmsDefaultContainerFactoryConfig.class, LazyBean.class);
		JmsListenerContainerTestFactory defaultFactory =
				context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class);
		assertEquals(0, defaultFactory.getListenerContainers().size());

		context.getBean(LazyBean.class);  // trigger lazy resolution
		assertEquals(1, defaultFactory.getListenerContainers().size());
		MessageListenerTestContainer container = defaultFactory.getListenerContainers().get(0);
		assertTrue("Should have been started " + container, container.isStarted());
		context.close();  // close and stop the listeners
		assertTrue("Should have been stopped " + container, container.isStopped());
	}


	@EnableJms
	@Configuration
	static class EnableJmsSampleConfig {

		@Bean
		public JmsListenerContainerTestFactory jmsListenerContainerFactory() {
			return new JmsListenerContainerTestFactory();
		}

		@Bean
		public JmsListenerContainerTestFactory simpleFactory() {
			return new JmsListenerContainerTestFactory();
		}
	}


	@EnableJms
	@Configuration
	static class EnableJmsFullConfig {

		@Bean
		public JmsListenerContainerTestFactory simpleFactory() {
			return new JmsListenerContainerTestFactory();
		}
	}


	@EnableJms
	@Configuration
	@PropertySource("classpath:/org/springframework/jms/annotation/jms-listener.properties")
	static class EnableJmsFullConfigurableConfig {

		@Bean
		public JmsListenerContainerTestFactory simpleFactory() {
			return new JmsListenerContainerTestFactory();
		}

		@Bean
		public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
			return new PropertySourcesPlaceholderConfigurer();
		}
	}


	@Configuration
	@EnableJms
	static class EnableJmsCustomConfig implements JmsListenerConfigurer {

		@Override
		public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
			registrar.setEndpointRegistry(customRegistry());

			// Also register a custom endpoint
			SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
			endpoint.setId("myCustomEndpointId");
			endpoint.setDestination("myQueue");
			endpoint.setMessageListener(simpleMessageListener());
			registrar.registerEndpoint(endpoint);
		}

		@Bean
		public JmsListenerContainerTestFactory jmsListenerContainerFactory() {
			return new JmsListenerContainerTestFactory();
		}

		@Bean
		public JmsListenerEndpointRegistry customRegistry() {
			return new JmsListenerEndpointRegistry();
		}

		@Bean
		public JmsListenerContainerTestFactory customFactory() {
			return new JmsListenerContainerTestFactory();
		}

		@Bean
		public MessageListener simpleMessageListener() {
			return new MessageListenerAdapter();
		}
	}


	@Configuration
	@EnableJms
	static class EnableJmsCustomContainerFactoryConfig implements JmsListenerConfigurer {

		@Override
		public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
			registrar.setContainerFactory(simpleFactory());
		}

		@Bean
		public JmsListenerContainerTestFactory simpleFactory() {
			return new JmsListenerContainerTestFactory();
		}
	}


	@Configuration
	@EnableJms
	static class EnableJmsDefaultContainerFactoryConfig {

		@Bean
		public JmsListenerContainerTestFactory jmsListenerContainerFactory() {
			return new JmsListenerContainerTestFactory();
		}
	}


	@Configuration
	@EnableJms
	static class EnableJmsHandlerMethodFactoryConfig implements JmsListenerConfigurer {

		@Override
		public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
			registrar.setMessageHandlerMethodFactory(customMessageHandlerMethodFactory());
		}

		@Bean
		public MessageHandlerMethodFactory customMessageHandlerMethodFactory() {
			DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
			factory.setValidator(new TestValidator());
			return factory;
		}

		@Bean
		public JmsListenerContainerTestFactory defaultFactory() {
			return new JmsListenerContainerTestFactory();
		}
	}


	@Configuration
	@EnableJms
	static class EnableJmsAutoStartupFalseConfig implements JmsListenerConfigurer {

		@Override
		public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
			registrar.setContainerFactory(simpleFactory());
		}

		@Bean
		public JmsListenerContainerTestFactory simpleFactory() {
			JmsListenerContainerTestFactory factory = new JmsListenerContainerTestFactory();
			factory.setAutoStartup(false);
			return factory;
		}
	}


	@Component
	@Lazy
	static class LazyBean {

		@JmsListener(destination = "myQueue")
		public void handle(String msg) {
		}
	}


	@JmsListener(destination = "orderQueue")
	@Retention(RetentionPolicy.RUNTIME)
	private @interface OrderQueueListener {

		@AliasFor(annotation = JmsListener.class)
		String id() default "";

		@AliasFor(annotation = JmsListener.class)
		String concurrency() default "";
	}


	@JmsListener(destination = "billingQueue")
	@Retention(RetentionPolicy.RUNTIME)
	private @interface BillingQueueListener {

		@AliasFor(annotation = JmsListener.class)
		String id() default "";

		@AliasFor(annotation = JmsListener.class)
		String concurrency() default "";
	}


	@Component
	static class ComposedJmsListenersBean {

		@OrderQueueListener(id = "first")
		@BillingQueueListener(id = "second", concurrency = "2-10")
		public void repeatableHandle(String msg) {
		}
	}

}
