/* * Copyright 2015-2017 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.config; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.BeansException; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.stream.binder.BinderFactory; import org.springframework.cloud.stream.binding.AbstractBindingTargetFactory; import org.springframework.cloud.stream.binding.BinderAwareChannelResolver; import org.springframework.cloud.stream.binding.BinderAwareRouterBeanPostProcessor; import org.springframework.cloud.stream.binding.BindingService; import org.springframework.cloud.stream.binding.CompositeMessageChannelConfigurer; import org.springframework.cloud.stream.binding.ContextStartAfterRefreshListener; import org.springframework.cloud.stream.binding.DynamicDestinationsBindable; import org.springframework.cloud.stream.binding.InputBindingLifecycle; import org.springframework.cloud.stream.binding.MessageChannelConfigurer; import org.springframework.cloud.stream.binding.MessageConverterConfigurer; import org.springframework.cloud.stream.binding.OutputBindingLifecycle; import org.springframework.cloud.stream.binding.SingleBindingTargetBindable; import org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor; import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory; import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.expression.PropertyAccessor; import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.json.JsonPropertyAccessor; import org.springframework.integration.support.DefaultMessageBuilderFactory; import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.core.DestinationResolutionException; import org.springframework.messaging.core.DestinationResolver; import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory; import org.springframework.tuple.spel.TuplePropertyAccessor; import org.springframework.util.CollectionUtils; /** * Configuration class that provides necessary beans for {@link MessageChannel} binding. * * @author Dave Syer * @author David Turanski * @author Marius Bogoevici * @author Ilayaperumal Gopinathan * @author Gary Russell */ @Configuration @EnableConfigurationProperties(BindingServiceProperties.class) public class BindingServiceConfiguration { private static final String ERROR_CHANNEL_NAME = "error"; public static final String STREAM_LISTENER_ANNOTATION_BEAN_POST_PROCESSOR_NAME = "streamListenerAnnotationBeanPostProcessor"; @Autowired(required = false) private ObjectMapper objectMapper; /** * User defined custom message converters */ @Autowired(required = false) private List<MessageConverter> customMessageConverters; @Bean // This conditional is intentionally not in an autoconfig (usually a bad idea) because // it is used to detect a BindingService in the parent context (which we know // already exists). @ConditionalOnMissingBean(BindingService.class) public BindingService bindingService(BindingServiceProperties bindingServiceProperties, BinderFactory binderFactory) { return new BindingService(bindingServiceProperties, binderFactory); } @Bean public MessageConverterConfigurer messageConverterConfigurer(BindingServiceProperties bindingServiceProperties, CompositeMessageConverterFactory compositeMessageConverterFactory) { return new MessageConverterConfigurer(bindingServiceProperties, compositeMessageConverterFactory); } @Bean public SubscribableChannelBindingTargetFactory channelFactory( CompositeMessageChannelConfigurer compositeMessageChannelConfigurer) { return new SubscribableChannelBindingTargetFactory(compositeMessageChannelConfigurer); } @Bean public CompositeMessageChannelConfigurer compositeMessageChannelConfigurer( MessageConverterConfigurer messageConverterConfigurer) { List<MessageChannelConfigurer> configurerList = new ArrayList<>(); configurerList.add(messageConverterConfigurer); return new CompositeMessageChannelConfigurer(configurerList); } @Bean @DependsOn("bindingService") public OutputBindingLifecycle outputBindingLifecycle() { return new OutputBindingLifecycle(); } @Bean @DependsOn("bindingService") public InputBindingLifecycle inputBindingLifecycle() { return new InputBindingLifecycle(); } @Bean @DependsOn("bindingService") public ContextStartAfterRefreshListener contextStartAfterRefreshListener() { return new ContextStartAfterRefreshListener(); } @Bean public BinderAwareChannelResolver binderAwareChannelResolver(BindingService bindingService, AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory, DynamicDestinationsBindable dynamicDestinationsBindable) { return new BinderAwareChannelResolver(bindingService, bindingTargetFactory, dynamicDestinationsBindable); } @Bean @ConditionalOnProperty("spring.cloud.stream.bindings." + ERROR_CHANNEL_NAME + ".destination") public SingleBindingTargetBindable<MessageChannel> errorChannelBindable( @Qualifier(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME) PublishSubscribeChannel errorChannel) { return new SingleBindingTargetBindable<MessageChannel>(ERROR_CHANNEL_NAME, errorChannel); } @Bean public DynamicDestinationsBindable dynamicDestinationsBindable() { return new DynamicDestinationsBindable(); } @Bean public CompositeMessageConverterFactory compositeMessageConverterFactory() { List<MessageConverter> messageConverters = new ArrayList<>(); if (!CollectionUtils.isEmpty(this.customMessageConverters)) { messageConverters.addAll(Collections.unmodifiableCollection(this.customMessageConverters)); } return new CompositeMessageConverterFactory(messageConverters, this.objectMapper); } @Bean public static MessageHandlerMethodFactory messageHandlerMethodFactory( CompositeMessageConverterFactory compositeMessageConverterFactory) { DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory(); messageHandlerMethodFactory .setMessageConverter(compositeMessageConverterFactory.getMessageConverterForAllRegistered()); return messageHandlerMethodFactory; } @Bean // provided for backwards compatibility scenarios public ChannelBindingServiceProperties channelBindingServiceProperties( BindingServiceProperties bindingServiceProperties) { return new ChannelBindingServiceProperties(bindingServiceProperties); } @Bean(name = STREAM_LISTENER_ANNOTATION_BEAN_POST_PROCESSOR_NAME) public static StreamListenerAnnotationBeanPostProcessor streamListenerAnnotationBeanPostProcessor() { return new StreamListenerAnnotationBeanPostProcessor(); } @Bean public ReadOnlyHeadersAdjuster readOnlyHeadersAdjuster() { return new ReadOnlyHeadersAdjuster(); } public static class ReadOnlyHeadersAdjuster implements SmartInitializingSingleton, BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public void afterSingletonsInstantiated() { if (beanFactory.containsBean(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME)) { try { DefaultMessageBuilderFactory messageBuilderFactory = beanFactory.getBean( IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME, DefaultMessageBuilderFactory.class); // TODO: Add 'addReadOnlyHeader' to builder - INT-4272 String[] readOnlyHeaders = (String[]) new DirectFieldAccessor(messageBuilderFactory) .getPropertyValue("readOnlyHeaders"); if (readOnlyHeaders == null || !Arrays.asList(readOnlyHeaders).contains(MessageHeaders.CONTENT_TYPE)) { String[] newReadOnlyHeaders = readOnlyHeaders == null ? new String[1] : Arrays.copyOf(readOnlyHeaders, readOnlyHeaders.length + 1); int pos = readOnlyHeaders != null ? readOnlyHeaders.length : 0; newReadOnlyHeaders[pos] = MessageHeaders.CONTENT_TYPE; messageBuilderFactory.setReadOnlyHeaders(newReadOnlyHeaders); } } catch (RuntimeException e) { // ignore } } } } // IMPORTANT: Nested class to avoid instantiating all of the above early @Configuration protected static class PostProcessorConfiguration { private BinderAwareChannelResolver binderAwareChannelResolver; @Bean @ConditionalOnMissingBean(BinderAwareRouterBeanPostProcessor.class) public BinderAwareRouterBeanPostProcessor binderAwareRouterBeanPostProcessor( final ConfigurableListableBeanFactory beanFactory) { // IMPORTANT: Lazy delegate to avoid instantiating all of the above early return new BinderAwareRouterBeanPostProcessor(new DestinationResolver<MessageChannel>() { @Override public MessageChannel resolveDestination(String name) throws DestinationResolutionException { if (PostProcessorConfiguration.this.binderAwareChannelResolver == null) { PostProcessorConfiguration.this.binderAwareChannelResolver = BeanFactoryUtils .beanOfType(beanFactory, BinderAwareChannelResolver.class); } return PostProcessorConfiguration.this.binderAwareChannelResolver.resolveDestination(name); } }); } /** * Adds property accessors for use in SpEL expression evaluation */ @Bean public static BeanPostProcessor propertyAccessorBeanPostProcessor() { final Map<String, PropertyAccessor> accessors = new HashMap<>(); accessors.put("tuplePropertyAccessor", new TuplePropertyAccessor()); accessors.put("jsonPropertyAccessor", new JsonPropertyAccessor()); return new BeanPostProcessor() { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME.equals(beanName)) { IntegrationEvaluationContextFactoryBean factoryBean = (IntegrationEvaluationContextFactoryBean) bean; Map<String, PropertyAccessor> factoryBeanAccessors = factoryBean.getPropertyAccessors(); for (Map.Entry<String, PropertyAccessor> entry : accessors.entrySet()) { if (!factoryBeanAccessors.containsKey(entry.getKey())) { factoryBeanAccessors.put(entry.getKey(), entry.getValue()); } } factoryBean.setPropertyAccessors(factoryBeanAccessors); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }; } } }