/* * Copyright 2013-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.cloud.stream.binder; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; import org.junit.Test; import org.springframework.cloud.stream.binding.MessageConverterConfigurer; import org.springframework.cloud.stream.config.BindingProperties; import org.springframework.cloud.stream.config.BindingServiceProperties; import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.Lifecycle; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.PollableChannel; import org.springframework.util.MimeTypeUtils; import static org.assertj.core.api.Assertions.assertThat; /** * @author Gary Russell * @author Ilayaperumal Gopinathan * @author David Turanski * @author Mark Fisher * @author Marius Bogoevici */ @SuppressWarnings("unchecked") public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends AbstractBinder<MessageChannel, CP, PP>, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> { protected final Log logger = LogFactory.getLog(this.getClass()); protected B testBinder; /** * Subclasses may override this default value to have tests wait longer for a message receive, for example if * running * in an environment that is known to be slow (e.g. travis). */ protected double timeoutMultiplier = 1.0D; /** * Attempt to receive a message on the given channel, * waiting up to 1s (times the {@link #timeoutMultiplier}). */ protected Message<?> receive(PollableChannel channel) { return receive(channel, 1); } /** * Attempt to receive a message on the given channel, * waiting up to 1s * additionalMultiplier * {@link #timeoutMultiplier}). * * Allows accomodating tests which are slower than normal (e.g. retry). */ protected Message<?> receive(PollableChannel channel, int additionalMultiplier) { long startTime = System.currentTimeMillis(); Message<?> receive = channel.receive((int) (1000 * timeoutMultiplier * additionalMultiplier)); long elapsed = System.currentTimeMillis() - startTime; logger.debug("receive() took " + elapsed / 1000 + " seconds"); return receive; } @Test public void testClean() throws Exception { Binder binder = getBinder(); Binding<MessageChannel> foo0ProducerBinding = binder.bindProducer("foo.0", new DirectChannel(), createProducerProperties()); Binding<MessageChannel> foo0ConsumerBinding = binder.bindConsumer("foo.0", "test", new DirectChannel(), createConsumerProperties()); Binding<MessageChannel> foo1ProducerBinding = binder.bindProducer("foo.1", new DirectChannel(), createProducerProperties()); Binding<MessageChannel> foo1ConsumerBinding = binder.bindConsumer("foo.1", "test", new DirectChannel(), createConsumerProperties()); Binding<MessageChannel> foo2ProducerBinding = binder.bindProducer("foo.2", new DirectChannel(), createProducerProperties()); foo0ProducerBinding.unbind(); assertThat(TestUtils.getPropertyValue(foo0ProducerBinding, "lifecycle", Lifecycle.class).isRunning()) .isFalse(); foo0ConsumerBinding.unbind(); foo1ProducerBinding.unbind(); assertThat(TestUtils.getPropertyValue(foo0ConsumerBinding, "lifecycle", Lifecycle.class).isRunning()) .isFalse(); assertThat(TestUtils.getPropertyValue(foo1ProducerBinding, "lifecycle", Lifecycle.class).isRunning()) .isFalse(); foo1ConsumerBinding.unbind(); foo2ProducerBinding.unbind(); assertThat(TestUtils.getPropertyValue(foo1ConsumerBinding, "lifecycle", Lifecycle.class).isRunning()) .isFalse(); assertThat(TestUtils.getPropertyValue(foo2ProducerBinding, "lifecycle", Lifecycle.class).isRunning()) .isFalse(); } @Test public void testSendAndReceive() throws Exception { Binder binder = getBinder(); BindingProperties outputBindingProperties = createProducerBindingProperties(createProducerProperties()); DirectChannel moduleOutputChannel = createBindableChannel("output", outputBindingProperties); QueueChannel moduleInputChannel = new QueueChannel(); Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, outputBindingProperties.getProducer()); Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "test", moduleInputChannel, createConsumerProperties()); Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar") .build(); // Let the consumer actually bind to the producer before sending a msg binderBindUnbindLatency(); moduleOutputChannel.send(message); Message<?> inbound = receive(moduleInputChannel); assertThat(inbound).isNotNull(); assertThat(inbound.getPayload()).isEqualTo("foo"); assertThat(inbound.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); assertThat(inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo("foo/bar"); producerBinding.unbind(); consumerBinding.unbind(); } @Test public void testSendAndReceiveMultipleTopics() throws Exception { Binder binder = getBinder(); DirectChannel moduleOutputChannel1 = createBindableChannel("output1", createProducerBindingProperties(createProducerProperties())); DirectChannel moduleOutputChannel2 = createBindableChannel("output2", createProducerBindingProperties(createProducerProperties())); QueueChannel moduleInputChannel = new QueueChannel(); Binding<MessageChannel> producerBinding1 = binder.bindProducer("foo.x", moduleOutputChannel1, createProducerProperties()); Binding<MessageChannel> producerBinding2 = binder.bindProducer("foo.y", moduleOutputChannel2, createProducerProperties()); Binding<MessageChannel> consumerBinding1 = binder.bindConsumer("foo.x", "test", moduleInputChannel, createConsumerProperties()); Binding<MessageChannel> consumerBinding2 = binder.bindConsumer("foo.y", "test", moduleInputChannel, createConsumerProperties()); String testPayload1 = "foo" + UUID.randomUUID().toString(); Message<?> message1 = MessageBuilder.withPayload(testPayload1.getBytes()).build(); String testPayload2 = "foo" + UUID.randomUUID().toString(); Message<?> message2 = MessageBuilder.withPayload(testPayload2.getBytes()).build(); // Let the consumer actually bind to the producer before sending a msg binderBindUnbindLatency(); moduleOutputChannel1.send(message1); moduleOutputChannel2.send(message2); Message<?>[] messages = new Message[2]; messages[0] = receive(moduleInputChannel); messages[1] = receive(moduleInputChannel); assertThat(messages[0]).isNotNull(); assertThat(messages[1]).isNotNull(); assertThat(messages).extracting("payload").containsExactlyInAnyOrder(testPayload1.getBytes(), testPayload2.getBytes()); producerBinding1.unbind(); producerBinding2.unbind(); consumerBinding1.unbind(); consumerBinding2.unbind(); } @Test public void testSendAndReceiveNoOriginalContentType() throws Exception { Binder binder = getBinder(); BindingProperties producerBindingProperties = createProducerBindingProperties(createProducerProperties()); DirectChannel moduleOutputChannel = createBindableChannel("output", producerBindingProperties); QueueChannel moduleInputChannel = new QueueChannel(); Binding<MessageChannel> producerBinding = binder.bindProducer("bar.0", moduleOutputChannel, producerBindingProperties.getProducer()); Binding<MessageChannel> consumerBinding = binder.bindConsumer("bar.0", "test", moduleInputChannel, createConsumerProperties()); binderBindUnbindLatency(); Message<?> message = MessageBuilder.withPayload("foo").build(); moduleOutputChannel.send(message); Message<?> inbound = receive(moduleInputChannel); assertThat(inbound).isNotNull(); assertThat(inbound.getPayload()).isEqualTo("foo"); assertThat(inbound.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); assertThat(inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.TEXT_PLAIN_VALUE); producerBinding.unbind(); consumerBinding.unbind(); } protected abstract B getBinder() throws Exception; protected abstract CP createConsumerProperties(); protected abstract PP createProducerProperties(); protected final BindingProperties createConsumerBindingProperties(CP consumerProperties) { BindingProperties bindingProperties = new BindingProperties(); bindingProperties.setConsumer(consumerProperties); return bindingProperties; } protected BindingProperties createProducerBindingProperties(PP producerProperties) { BindingProperties bindingProperties = new BindingProperties(); bindingProperties.setProducer(producerProperties); return bindingProperties; } protected DirectChannel createBindableChannel(String channelName, BindingProperties bindingProperties) throws Exception { BindingServiceProperties bindingServiceProperties = new BindingServiceProperties(); bindingServiceProperties.getBindings().put(channelName, bindingProperties); ConfigurableApplicationContext applicationContext = new GenericApplicationContext(); applicationContext.refresh(); bindingServiceProperties.setApplicationContext(applicationContext); bindingServiceProperties.setConversionService(new DefaultConversionService()); bindingServiceProperties.afterPropertiesSet(); DirectChannel channel = new DirectChannel(); channel.setBeanName(channelName); MessageConverterConfigurer messageConverterConfigurer = new MessageConverterConfigurer( bindingServiceProperties, new CompositeMessageConverterFactory(null, null)); messageConverterConfigurer.setBeanFactory(applicationContext.getBeanFactory()); messageConverterConfigurer.afterPropertiesSet(); messageConverterConfigurer.configureOutputChannel(channel, channelName); return channel; } @After public void cleanup() { if (testBinder != null) { testBinder.cleanup(); } } /** * If appropriate, let the binder middleware settle down a bit while binding/unbinding actually happens. */ protected void binderBindUnbindLatency() throws InterruptedException { // default none } /** * Create a new spy on the given 'queue'. This allows de-correlating the creation of * the 'connection' from its actual usage, which may be needed by some implementations * to see messages sent after connection creation. */ public abstract Spy spyOn(final String name); }