/* * 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.cloud.stream.binding.AbstractBindingTargetFactory; import org.springframework.cloud.stream.binding.BinderAwareChannelResolver; import org.springframework.cloud.stream.binding.BindingService; import org.springframework.cloud.stream.binding.DynamicDestinationsBindable; import org.springframework.cloud.stream.binding.InputBindingLifecycle; import org.springframework.cloud.stream.binding.MessageConverterConfigurer; import org.springframework.cloud.stream.binding.OutputBindingLifecycle; import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory; 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.support.StaticApplicationContext; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.support.DefaultMessageBuilderFactory; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.messaging.SubscribableChannel; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.matches; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * @author Mark Fisher * @author Gary Russell * @author Ilayaperumal Gopinathan */ public class BinderAwareChannelResolverTests { protected final StaticApplicationContext context = new StaticApplicationContext(); protected volatile BinderAwareChannelResolver resolver; protected volatile Binder<MessageChannel, ConsumerProperties, ProducerProperties> binder; protected volatile AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory; protected volatile BindingServiceProperties bindingServiceProperties; protected volatile DynamicDestinationsBindable dynamicDestinationsBindable; private volatile List<TestBinder.TestBinding> producerBindings; @Before public void setupContext() throws Exception { producerBindings = new ArrayList<>(); this.binder = new TestBinder(); BinderFactory binderFactory = new BinderFactory() { @Override public <T> Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties> getBinder(String configurationName, Class<? extends T> bindableType) { return (Binder<T, ? extends ConsumerProperties, ? extends ProducerProperties>) binder; } }; this.bindingServiceProperties = new BindingServiceProperties(); Map<String, BindingProperties> bindings = new HashMap<>(); BindingProperties bindingProperties = new BindingProperties(); bindingProperties.setContentType("text/plain"); bindings.put("foo", bindingProperties); this.bindingServiceProperties.setBindings(bindings); BindingService bindingService = new BindingService(bindingServiceProperties, binderFactory); MessageConverterConfigurer messageConverterConfigurer = new MessageConverterConfigurer( this.bindingServiceProperties, new CompositeMessageConverterFactory()); messageConverterConfigurer.setBeanFactory(Mockito.mock(ConfigurableListableBeanFactory.class)); messageConverterConfigurer.afterPropertiesSet(); this.bindingTargetFactory = new SubscribableChannelBindingTargetFactory(messageConverterConfigurer); dynamicDestinationsBindable = new DynamicDestinationsBindable(); this.resolver = new BinderAwareChannelResolver(bindingService, this.bindingTargetFactory, dynamicDestinationsBindable); this.resolver.setBeanFactory(context.getBeanFactory()); context.getBeanFactory().registerSingleton("channelResolver", this.resolver); context.getBeanFactory().registerSingleton("dynamicDestinationBindable", this.dynamicDestinationsBindable); context.registerSingleton("other", DirectChannel.class); context.registerSingleton(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME, DefaultMessageBuilderFactory.class); context.getBeanFactory().registerSingleton("bindingService", bindingService); context.registerSingleton("inputBindingLifecycle", InputBindingLifecycle.class); context.registerSingleton("outputBindingLifecycle", OutputBindingLifecycle.class); context.refresh(); } @Test public void resolveChannel() { assertThat(producerBindings).hasSize(0); MessageChannel registered = resolver.resolveDestination("foo"); assertThat(producerBindings).hasSize(1); TestBinder.TestBinding binding = producerBindings.get(0); assertThat(binding.isBound()).describedAs("Must be bound"); DirectChannel testChannel = new DirectChannel(); final CountDownLatch latch = new CountDownLatch(1); final List<Message<?>> received = new ArrayList<>(); testChannel.subscribe(new MessageHandler() { @Override public void handleMessage(Message<?> message) throws MessagingException { received.add(message); latch.countDown(); } }); binder.bindConsumer("foo", null, testChannel, new ConsumerProperties()); assertThat(received).hasSize(0); registered.send(MessageBuilder.withPayload("hello").build()); try { assertThat(latch.await(1, TimeUnit.SECONDS)).describedAs("Latch timed out"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); fail("interrupted while awaiting latch"); } assertThat(received).hasSize(1); assertThat(received.get(0).getPayload()).isEqualTo("hello"); context.close(); assertThat(producerBindings).hasSize(1); assertThat(binding.isBound()).isFalse().describedAs("Must not be bound"); } @Test public void resolveNonRegisteredChannel() { MessageChannel other = resolver.resolveDestination("other"); assertThat(context.getBean("other")).isSameAs(other); } @Test @SuppressWarnings({"rawtypes", "unchecked"}) public void propertyPassthrough() { Map<String, BindingProperties> bindings = new HashMap<>(); BindingProperties genericProperties = new BindingProperties(); genericProperties.setContentType("text/plain"); bindings.put("foo", genericProperties); this.bindingServiceProperties.setBindings(bindings); @SuppressWarnings("unchecked") Binder binder = mock(Binder.class); Binder binder2 = mock(Binder.class); BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class); Binding<MessageChannel> fooBinding = Mockito.mock(Binding.class); Binding<MessageChannel> barBinding = Mockito.mock(Binding.class); when(binder.bindProducer( matches("foo"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(fooBinding); when(binder2.bindProducer( matches("bar"), any(DirectChannel.class), any(ProducerProperties.class))).thenReturn(barBinding); when(mockBinderFactory.getBinder(null, DirectChannel.class)).thenReturn(binder); when(mockBinderFactory.getBinder("someTransport", DirectChannel.class)).thenReturn(binder2); BindingService bindingService = new BindingService(bindingServiceProperties, mockBinderFactory); @SuppressWarnings("unchecked") BinderAwareChannelResolver resolver = new BinderAwareChannelResolver(bindingService, this.bindingTargetFactory, new DynamicDestinationsBindable()); BeanFactory beanFactory = new DefaultListableBeanFactory(); resolver.setBeanFactory(beanFactory); SubscribableChannel resolved = (SubscribableChannel) resolver.resolveDestination("foo"); verify(binder).bindProducer(eq("foo"), any(MessageChannel.class), any(ProducerProperties.class)); assertThat(resolved).isSameAs(beanFactory.getBean("foo")); } /** * A simple test binder that creates queues for the destinations. Ignores groups. */ private class TestBinder implements Binder<MessageChannel, ConsumerProperties, ProducerProperties> { private final Map<String, DirectChannel> destinations = new ConcurrentHashMap<>(); @Override public Binding<MessageChannel> bindConsumer(String name, String group, MessageChannel inboundBindTarget, ConsumerProperties properties) { synchronized (destinations) { if (!destinations.containsKey(name)) { destinations.put(name, new DirectChannel()); } } DirectHandler directHandler = new DirectHandler(inboundBindTarget); destinations.get(name).subscribe(directHandler); return new TestBinding(name, directHandler); } @Override public Binding<MessageChannel> bindProducer(String name, MessageChannel outboundBindTarget, ProducerProperties properties) { synchronized (destinations) { if (!destinations.containsKey(name)) { destinations.put(name, new DirectChannel()); } } DirectHandler directHandler = new DirectHandler(destinations.get(name)); // for test purposes we can assume it is a SubscribableChannel ((SubscribableChannel) outboundBindTarget).subscribe(directHandler); TestBinding binding = new TestBinding(name, directHandler); producerBindings.add(binding); return binding; } private final class TestBinding implements Binding<MessageChannel> { private final String name; private final DirectHandler directHandler; private boolean bound = true; private TestBinding(String name, DirectHandler directHandler) { this.name = name; this.directHandler = directHandler; } @Override public void unbind() { bound = false; destinations.get(name).unsubscribe(directHandler); } public boolean isBound() { return bound; } } } }