/* * Copyright 2015-2018 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 * * https://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.binding; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.Input; import org.springframework.cloud.stream.annotation.Output; import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.BinderConfiguration; import org.springframework.cloud.stream.binder.BinderFactory; import org.springframework.cloud.stream.binder.BinderType; import org.springframework.cloud.stream.binder.BinderTypeRegistry; import org.springframework.cloud.stream.binder.Binding; import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.binder.DefaultBinderFactory; import org.springframework.cloud.stream.binder.DefaultBinderTypeRegistry; import org.springframework.cloud.stream.binder.ExtendedProducerProperties; import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder; import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; import org.springframework.cloud.stream.config.BindingProperties; import org.springframework.cloud.stream.config.BindingServiceConfiguration; import org.springframework.cloud.stream.config.BindingServiceProperties; import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory; import org.springframework.cloud.stream.messaging.Processor; import org.springframework.cloud.stream.messaging.Sink; import org.springframework.cloud.stream.reflection.GenericsUtils; import org.springframework.cloud.stream.utils.MockBinderConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.core.DestinationResolutionException; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.matches; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * @author Gary Russell * @author Mark Fisher * @author Marius Bogoevici * @author Ilayaperumal Gopinathan * @author Janne Valkealahti * @author Soby Chacko * @author Michael Michailidis */ public class BindingServiceTests { @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testDefaultGroup() throws Exception { BindingServiceProperties properties = new BindingServiceProperties(); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); props.setDestination("foo"); final String inputChannelName = "input"; bindingProperties.put(inputChannelName, props); properties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); Binder binder = binderFactory.getBinder("mock", MessageChannel.class); BindingService service = new BindingService(properties, binderFactory); MessageChannel inputChannel = new DirectChannel(); Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class); when(binder.bindConsumer(eq("foo"), isNull(), same(inputChannel), any(ConsumerProperties.class))).thenReturn(mockBinding); Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, inputChannelName); assertThat(bindings).hasSize(1); Binding<MessageChannel> binding = bindings.iterator().next(); assertThat(binding).isSameAs(mockBinding); service.unbindConsumers(inputChannelName); verify(binder).bindConsumer(eq("foo"), isNull(), same(inputChannel), any(ConsumerProperties.class)); verify(binding).unbind(); binderFactory.destroy(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testMultipleConsumerBindings() throws Exception { BindingServiceProperties properties = new BindingServiceProperties(); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); props.setDestination("foo,bar"); final String inputChannelName = "input"; bindingProperties.put(inputChannelName, props); properties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); Binder binder = binderFactory.getBinder("mock", MessageChannel.class); BindingService service = new BindingService(properties, binderFactory); MessageChannel inputChannel = new DirectChannel(); Binding<MessageChannel> mockBinding1 = Mockito.mock(Binding.class); Binding<MessageChannel> mockBinding2 = Mockito.mock(Binding.class); when(binder.bindConsumer(eq("foo"), isNull(), same(inputChannel), any(ConsumerProperties.class))).thenReturn(mockBinding1); when(binder.bindConsumer(eq("bar"), isNull(), same(inputChannel), any(ConsumerProperties.class))).thenReturn(mockBinding2); Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, "input"); assertThat(bindings).hasSize(2); Iterator<Binding<MessageChannel>> iterator = bindings.iterator(); Binding<MessageChannel> binding1 = iterator.next(); Binding<MessageChannel> binding2 = iterator.next(); assertThat(binding1).isSameAs(mockBinding1); assertThat(binding2).isSameAs(mockBinding2); service.unbindConsumers("input"); verify(binder).bindConsumer(eq("foo"), isNull(), same(inputChannel), any(ConsumerProperties.class)); verify(binder).bindConsumer(eq("bar"), isNull(), same(inputChannel), any(ConsumerProperties.class)); verify(binding1).unbind(); verify(binding2).unbind(); binderFactory.destroy(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testMultipleConsumerBindingsFromIndexList() throws Exception { BindingServiceProperties properties = new BindingServiceProperties(); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); props.setDestination("foo"); ConsumerProperties consumer = properties.getConsumerProperties("input"); consumer.setInstanceIndexList(Arrays.asList(0, 1)); consumer.setInstanceCount(2); consumer.setPartitioned(true); props.setConsumer(consumer); final String inputChannelName = "input"; bindingProperties.put(inputChannelName, props); properties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); Binder binder = binderFactory.getBinder("mock", MessageChannel.class); BindingService service = new BindingService(properties, binderFactory); MessageChannel inputChannel = new DirectChannel(); Binding<MessageChannel> mockBinding1 = Mockito.mock(Binding.class, "FirstBinding"); Binding<MessageChannel> mockBinding2 = Mockito.mock(Binding.class, "SecondBinding"); ArgumentCaptor<ConsumerProperties> captor = ArgumentCaptor.forClass(ConsumerProperties.class); when(binder.bindConsumer(eq("foo"), isNull(), same(inputChannel), any(ConsumerProperties.class))).thenReturn(mockBinding1).thenReturn(mockBinding2); Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, "input"); assertThat(bindings).hasSize(2); Iterator<Binding<MessageChannel>> iterator = bindings.iterator(); Binding<MessageChannel> binding1 = iterator.next(); Binding<MessageChannel> binding2 = iterator.next(); assertThat(binding1).isSameAs(mockBinding1); assertThat(binding2).isSameAs(mockBinding2); service.unbindConsumers("input"); verify(binder, times(2)).bindConsumer(eq("foo"), isNull(), same(inputChannel), captor.capture()); verify(binding1).unbind(); verify(binding2).unbind(); List<ConsumerProperties> allValues = captor.getAllValues(); assertThat(allValues.size()).isEqualTo(2); assertThat(allValues.get(0).getInstanceIndex()).isEqualTo(0); assertThat(allValues.get(1).getInstanceIndex()).isEqualTo(1); binderFactory.destroy(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testConsumerBindingWhenMultiplexingIsEnabled() throws Exception { BindingServiceProperties properties = new BindingServiceProperties(); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); props.setDestination("foo,bar"); ConsumerProperties consumer = properties.getConsumerProperties("input"); consumer.setMultiplex(true); props.setConsumer(consumer); final String inputChannelName = "input"; bindingProperties.put(inputChannelName, props); properties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); Binder binder = binderFactory.getBinder("mock", MessageChannel.class); BindingService service = new BindingService(properties, binderFactory); MessageChannel inputChannel = new DirectChannel(); Binding<MessageChannel> mockBinding1 = Mockito.mock(Binding.class); when(binder.bindConsumer(eq("foo,bar"), isNull(), same(inputChannel), any(ConsumerProperties.class))).thenReturn(mockBinding1); Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, "input"); assertThat(bindings).hasSize(1); Iterator<Binding<MessageChannel>> iterator = bindings.iterator(); Binding<MessageChannel> binding1 = iterator.next(); assertThat(binding1).isSameAs(mockBinding1); service.unbindConsumers("input"); verify(binder).bindConsumer(eq("foo,bar"), isNull(), same(inputChannel), any(ConsumerProperties.class)); verify(binding1).unbind(); binderFactory.destroy(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testExplicitGroup() throws Exception { BindingServiceProperties properties = new BindingServiceProperties(); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); props.setDestination("foo"); props.setGroup("fooGroup"); final String inputChannelName = "input"; bindingProperties.put(inputChannelName, props); properties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); Binder binder = binderFactory.getBinder("mock", MessageChannel.class); BindingService service = new BindingService(properties, binderFactory); MessageChannel inputChannel = new DirectChannel(); Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class); when(binder.bindConsumer(eq("foo"), eq("fooGroup"), same(inputChannel), any(ConsumerProperties.class))).thenReturn(mockBinding); Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, inputChannelName); assertThat(bindings).hasSize(1); Binding<MessageChannel> binding = bindings.iterator().next(); assertThat(binding).isSameAs(mockBinding); service.unbindConsumers(inputChannelName); verify(binder).bindConsumer(eq("foo"), eq(props.getGroup()), same(inputChannel), any(ConsumerProperties.class)); verify(binding).unbind(); binderFactory.destroy(); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void checkDynamicBinding() { BindingServiceProperties properties = new BindingServiceProperties(); BindingProperties bindingProperties = new BindingProperties(); bindingProperties.setProducer(new ProducerProperties()); properties.setBindings(Collections.singletonMap("foo", bindingProperties)); DefaultBinderFactory binderFactory = createMockBinderFactory(); final ExtendedPropertiesBinder binder = mock(ExtendedPropertiesBinder.class); Properties extendedProps = new Properties(); when(binder.getExtendedProducerProperties(anyString())).thenReturn(extendedProps); Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class); final AtomicReference<MessageChannel> dynamic = new AtomicReference<>(); when(binder.bindProducer(matches("foo"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(mockBinding); BindingService bindingService = new BindingService(properties, binderFactory) { @Override protected <T> Binder<T, ?, ?> getBinder(String channelName, Class<T> bindableType) { return binder; } }; SubscribableChannelBindingTargetFactory bindableSubscribableChannelFactory; bindableSubscribableChannelFactory = new SubscribableChannelBindingTargetFactory( new MessageConverterConfigurer(properties, new CompositeMessageConverterFactory().getMessageConverterForAllRegistered())); final AtomicBoolean callbackInvoked = new AtomicBoolean(); BinderAwareChannelResolver resolver = new BinderAwareChannelResolver( bindingService, bindableSubscribableChannelFactory, new DynamicDestinationsBindable(), (name, channel, props, extended) -> { callbackInvoked.set(true); assertThat(name).isEqualTo("foo"); assertThat(channel).isNotNull(); assertThat(props).isNotNull(); assertThat(extended).isSameAs(extendedProps); props.setUseNativeEncoding(true); extendedProps.setProperty("bar", "baz"); }); ConfigurableListableBeanFactory beanFactory = mock( ConfigurableListableBeanFactory.class); when(beanFactory.getBean("foo", MessageChannel.class)) .thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class)); when(beanFactory.getBean("bar", MessageChannel.class)) .thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class)); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { dynamic.set(invocation.getArgument(1)); return null; } }).when(beanFactory).registerSingleton(eq("foo"), any(MessageChannel.class)); doAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return dynamic.get(); } }).when(beanFactory).initializeBean(any(MessageChannel.class), eq("foo")); resolver.setBeanFactory(beanFactory); MessageChannel resolved = resolver.resolveDestination("foo"); assertThat(resolved).isSameAs(dynamic.get()); ArgumentCaptor<ProducerProperties> captor = ArgumentCaptor .forClass(ProducerProperties.class); verify(binder).bindProducer(eq("foo"), eq(dynamic.get()), captor.capture()); assertThat(captor.getValue().isUseNativeEncoding()).isTrue(); assertThat(captor.getValue()).isInstanceOf(ExtendedProducerProperties.class); assertThat(((ExtendedProducerProperties) captor.getValue()).getExtension()) .isSameAs(extendedProps); doReturn(dynamic.get()).when(beanFactory).getBean("foo", MessageChannel.class); properties.setDynamicDestinations(new String[] { "foo" }); resolved = resolver.resolveDestination("foo"); assertThat(resolved).isSameAs(dynamic.get()); properties.setDynamicDestinations(new String[] { "test" }); try { resolver.resolveDestination("bar"); fail("Should throw an exception"); } catch (DestinationResolutionException e) { assertThat(e).hasMessageContaining( "Failed to find MessageChannel bean with name 'bar'"); } } @Test public void testProducerPropertiesValidation() { BindingServiceProperties serviceProperties = new BindingServiceProperties(); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); ProducerProperties producerProperties = new ProducerProperties(); producerProperties.setPartitionCount(0); props.setDestination("foo"); props.setProducer(producerProperties); final String outputChannelName = "output"; bindingProperties.put(outputChannelName, props); serviceProperties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); BindingService service = new BindingService(serviceProperties, binderFactory); MessageChannel outputChannel = new DirectChannel(); try { service.bindProducer(outputChannel, outputChannelName); fail("Producer properties should be validated."); } catch (IllegalStateException e) { assertThat(e) .hasMessageContaining("Partition count should be greater than zero."); } } @Test public void testDefaultPropertyBehavior() { ConfigurableApplicationContext run = SpringApplication.run( DefaultConsumerPropertiesTestSink.class, "--server.port=0", "--spring.cloud.stream.default.contentType=text/plain", "--spring.cloud.stream.bindings.input1.contentType=application/json", "--spring.cloud.stream.default.group=foo", "--spring.cloud.stream.bindings.input2.group=bar", "--spring.cloud.stream.default.consumer.concurrency=5", "--spring.cloud.stream.bindings.input2.consumer.concurrency=1", "--spring.cloud.stream.bindings.input1.consumer.partitioned=true", "--spring.cloud.stream.default.producer.partitionCount=10", "--spring.cloud.stream.bindings.output2.producer.partitionCount=1", "--spring.cloud.stream.bindings.inputXyz.contentType=application/json", "--spring.cloud.stream.bindings.inputFooBar.contentType=application/avro", "--spring.cloud.stream.bindings.input_snake_case.contentType=application/avro"); BindingServiceProperties bindingServiceProperties = run.getBeanFactory() .getBean(BindingServiceProperties.class); Map<String, BindingProperties> bindings = bindingServiceProperties.getBindings(); assertThat(bindings.get("input1").getContentType()).isEqualTo("application/json"); assertThat(bindings.get("input2").getContentType()).isEqualTo("text/plain"); assertThat(bindings.get("input1").getGroup()).isEqualTo("foo"); assertThat(bindings.get("input2").getGroup()).isEqualTo("bar"); assertThat(bindings.get("input1").getConsumer().getConcurrency()).isEqualTo(5); assertThat(bindings.get("input2").getConsumer().getConcurrency()).isEqualTo(1); assertThat(bindings.get("input1").getConsumer().isPartitioned()).isEqualTo(true); assertThat(bindings.get("input2").getConsumer().isPartitioned()).isEqualTo(false); assertThat(bindings.get("output1").getProducer().getPartitionCount()) .isEqualTo(10); assertThat(bindings.get("output2").getProducer().getPartitionCount()) .isEqualTo(1); assertThat(bindings.get("inputXyz").getContentType()) .isEqualTo("application/json"); assertThat(bindings.get("inputFooBar").getContentType()) .isEqualTo("application/avro"); assertThat(bindings.get("inputFooBarBuzz").getContentType()) .isEqualTo("text/plain"); assertThat(bindings.get("input_snake_case").getContentType()) .isEqualTo("application/avro"); run.close(); } @Test public void testConsumerPropertiesValidation() { BindingServiceProperties serviceProperties = new BindingServiceProperties(); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); ConsumerProperties consumerProperties = new ConsumerProperties(); consumerProperties.setConcurrency(0); props.setDestination("foo"); props.setConsumer(consumerProperties); final String inputChannelName = "input"; bindingProperties.put(inputChannelName, props); serviceProperties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); BindingService service = new BindingService(serviceProperties, binderFactory); MessageChannel inputChannel = new DirectChannel(); try { service.bindConsumer(inputChannel, inputChannelName); fail("Consumer properties should be validated."); } catch (IllegalStateException e) { assertThat(e) .hasMessageContaining("Concurrency should be greater than zero."); } } @Test public void testUnknownBinderOnBindingFailure() { HashMap<String, String> properties = new HashMap<>(); properties.put("spring.cloud.stream.bindings.input.destination", "fooInput"); properties.put("spring.cloud.stream.bindings.input.binder", "mock"); properties.put("spring.cloud.stream.bindings.output.destination", "fooOutput"); properties.put("spring.cloud.stream.bindings.output.binder", "mockError"); BindingServiceProperties bindingServiceProperties = createBindingServiceProperties( properties); BindingService bindingService = new BindingService(bindingServiceProperties, createMockBinderFactory()); bindingService.bindConsumer(new DirectChannel(), "input"); try { bindingService.bindProducer(new DirectChannel(), "output"); fail("Expected 'Unknown binder configuration'"); } catch (IllegalStateException e) { assertThat(e).hasMessageContaining("Unknown binder configuration: mockError"); } } @Test public void testUnrecognizedBinderAllowedIfNotUsed() { HashMap<String, String> properties = new HashMap<>(); properties.put("spring.cloud.stream.bindings.input.destination", "fooInput"); properties.put("spring.cloud.stream.bindings.output.destination", "fooOutput"); properties.put("spring.cloud.stream.defaultBinder", "mock1"); properties.put("spring.cloud.stream.binders.mock1.type", "mock"); properties.put("spring.cloud.stream.binders.kafka1.type", "kafka"); BindingServiceProperties bindingServiceProperties = createBindingServiceProperties( properties); BinderFactory binderFactory = new BindingServiceConfiguration() .binderFactory(createMockBinderTypeRegistry(), bindingServiceProperties); BindingService bindingService = new BindingService(bindingServiceProperties, binderFactory); bindingService.bindConsumer(new DirectChannel(), "input"); bindingService.bindProducer(new DirectChannel(), "output"); } @Test public void testUnrecognizedBinderDisallowedIfUsed() { HashMap<String, String> properties = new HashMap<>(); properties.put("spring.cloud.stream.bindings.input.destination", "fooInput"); properties.put("spring.cloud.stream.bindings.input.binder", "mock1"); properties.put("spring.cloud.stream.bindings.output.destination", "fooOutput"); properties.put("spring.cloud.stream.bindings.output.type", "kafka1"); properties.put("spring.cloud.stream.binders.mock1.type", "mock"); properties.put("spring.cloud.stream.binders.kafka1.type", "kafka"); BindingServiceProperties bindingServiceProperties = createBindingServiceProperties( properties); BinderFactory binderFactory = new BindingServiceConfiguration() .binderFactory(createMockBinderTypeRegistry(), bindingServiceProperties); BindingService bindingService = new BindingService(bindingServiceProperties, binderFactory); bindingService.bindConsumer(new DirectChannel(), "input"); try { bindingService.bindProducer(new DirectChannel(), "output"); fail("Expected 'Unknown binder configuration'"); } catch (IllegalArgumentException e) { assertThat(e).hasMessageContaining("Binder type kafka is not defined"); } } @Test public void testResolveBindableType() { Class<?> bindableType = GenericsUtils.getParameterType(FooBinder.class, Binder.class, 0); assertThat(bindableType).isSameAs(SomeBindableType.class); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Test @Ignore public void testLateBindingConsumer() throws Exception { BindingServiceProperties properties = new BindingServiceProperties(); properties.setBindingRetryInterval(1); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); props.setDestination("foo"); final String inputChannelName = "input"; bindingProperties.put(inputChannelName, props); properties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); Binder binder = binderFactory.getBinder("mock", MessageChannel.class); ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.initialize(); BindingService service = new BindingService(properties, binderFactory, scheduler); MessageChannel inputChannel = new DirectChannel(); final Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class); final CountDownLatch fail = new CountDownLatch(2); doAnswer(i -> { fail.countDown(); if (fail.getCount() == 1) { throw new RuntimeException("fail"); } return mockBinding; }).when(binder).bindConsumer(eq("foo"), isNull(), same(inputChannel), any(ConsumerProperties.class)); Collection<Binding<MessageChannel>> bindings = service.bindConsumer(inputChannel, inputChannelName); assertThat(fail.await(10, TimeUnit.SECONDS)).isTrue(); assertThat(bindings).hasSize(1); Binding<MessageChannel> delegate = TestUtils .getPropertyValue(bindings.iterator().next(), "delegate", Binding.class); int n = 0; while (n++ < 300 && delegate == null) { Thread.sleep(400); } assertThat(delegate).isSameAs(mockBinding); service.unbindConsumers(inputChannelName); verify(binder, times(2)).bindConsumer(eq("foo"), isNull(), same(inputChannel), any(ConsumerProperties.class)); verify(delegate).unbind(); binderFactory.destroy(); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Test public void testLateBindingProducer() throws Exception { BindingServiceProperties properties = new BindingServiceProperties(); properties.setBindingRetryInterval(1); Map<String, BindingProperties> bindingProperties = new HashMap<>(); BindingProperties props = new BindingProperties(); props.setDestination("foo"); final String outputChannelName = "output"; bindingProperties.put(outputChannelName, props); properties.setBindings(bindingProperties); DefaultBinderFactory binderFactory = createMockBinderFactory(); Binder binder = binderFactory.getBinder("mock", MessageChannel.class); ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.initialize(); BindingService service = new BindingService(properties, binderFactory, scheduler); MessageChannel outputChannel = new DirectChannel(); final Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class); final CountDownLatch fail = new CountDownLatch(2); doAnswer(i -> { fail.countDown(); if (fail.getCount() == 1) { throw new RuntimeException("fail"); } return mockBinding; }).when(binder).bindProducer(eq("foo"), same(outputChannel), any(ProducerProperties.class)); Binding<MessageChannel> binding = service.bindProducer(outputChannel, outputChannelName); assertThat(fail.await(10, TimeUnit.SECONDS)).isTrue(); assertThat(binding).isNotNull(); Binding delegate = TestUtils.getPropertyValue(binding, "delegate", Binding.class); int n = 0; while (n++ < 300 && delegate == null) { Thread.sleep(100); delegate = TestUtils.getPropertyValue(binding, "delegate", Binding.class); } assertThat(delegate).isSameAs(mockBinding); service.unbindProducers(outputChannelName); verify(binder, times(2)).bindProducer(eq("foo"), same(outputChannel), any(ProducerProperties.class)); verify(delegate).unbind(); binderFactory.destroy(); scheduler.destroy(); } @SuppressWarnings("unchecked") @Test public void testBindingAutostartup() throws Exception { ApplicationContext context = new SpringApplicationBuilder(FooConfiguration.class) .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false", "--spring.cloud.stream.bindings.input.consumer.auto-startup=false"); BindingService bindingService = context.getBean(BindingService.class); Field cbField = ReflectionUtils.findField(BindingService.class, "consumerBindings"); cbField.setAccessible(true); Map<String, Object> cbMap = (Map<String, Object>) cbField.get(bindingService); Binding<?> inputBinding = ((List<Binding<?>>) cbMap.get("input")).get(0); assertThat(inputBinding.isRunning()).isFalse(); } private DefaultBinderFactory createMockBinderFactory() { BinderTypeRegistry binderTypeRegistry = createMockBinderTypeRegistry(); return new DefaultBinderFactory( Collections.singletonMap("mock", new BinderConfiguration("mock", new HashMap<>(), true, true)), binderTypeRegistry); } private DefaultBinderTypeRegistry createMockBinderTypeRegistry() { return new DefaultBinderTypeRegistry(Collections.singletonMap("mock", new BinderType("mock", new Class[] { MockBinderConfiguration.class }))); } private BindingServiceProperties createBindingServiceProperties( HashMap<String, String> properties) { BindingServiceProperties bindingServiceProperties = new BindingServiceProperties(); org.springframework.boot.context.properties.bind.Binder propertiesBinder; propertiesBinder = new org.springframework.boot.context.properties.bind.Binder( new MapConfigurationPropertySource(properties)); propertiesBinder.bind("spring.cloud.stream", org.springframework.boot.context.properties.bind.Bindable .ofInstance(bindingServiceProperties)); return bindingServiceProperties; } public interface FooBinding { @Input("input1") SubscribableChannel in1(); @Input("input2") SubscribableChannel in2(); @Output("output1") MessageChannel out1(); @Output("output2") MessageChannel out2(); @Input("inputXyz") SubscribableChannel inXyz(); @Input("inputFooBar") SubscribableChannel inFooBar(); @Input("inputFooBarBuzz") SubscribableChannel inFooBarBuzz(); @Input("input_snake_case") SubscribableChannel inWithSnakeCase(); } @EnableBinding(FooBinding.class) @EnableAutoConfiguration public static class DefaultConsumerPropertiesTestSink { @Bean public Binder<?, ?, ?> binder() { return Mockito.mock(Binder.class, Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)); } } @EnableBinding(Sink.class) @Import(TestChannelBinderConfiguration.class) @EnableAutoConfiguration public static class FooConfiguration { @ServiceActivator(inputChannel = Processor.INPUT) public void echo(Message<?> value) throws Exception { } } public static class FooBinder implements Binder<SomeBindableType, ConsumerProperties, ProducerProperties> { @Override public Binding<SomeBindableType> bindConsumer(String name, String group, SomeBindableType inboundBindTarget, ConsumerProperties consumerProperties) { throw new UnsupportedOperationException(); } @Override public Binding<SomeBindableType> bindProducer(String name, SomeBindableType outboundBindTarget, ProducerProperties producerProperties) { throw new UnsupportedOperationException(); } } public static class SomeBindableType { } }