/*
* 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;
}
};
}
}
}