diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/HandlerMethodArgumentResolversHolder.java b/spring-integration-core/src/main/java/org/springframework/integration/config/HandlerMethodArgumentResolversHolder.java new file mode 100644 index 0000000000..1a1b20a514 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/HandlerMethodArgumentResolversHolder.java @@ -0,0 +1,52 @@ +/* + * Copyright 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.integration.config; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; + +/** + * A holder for the configured argument resolvers. + * + * @author Gary Russell + * @since 5.0 + * + */ +public class HandlerMethodArgumentResolversHolder { + + private final List resolvers; + + public HandlerMethodArgumentResolversHolder(List resolvers) { + this.resolvers = new ArrayList<>(resolvers); + } + + public List getResolvers() { + return Collections.unmodifiableList(this.resolvers); + } + + public void addResolver(HandlerMethodArgumentResolver resolver) { + this.resolvers.add(resolver); + } + + public boolean removeResolver(HandlerMethodArgumentResolver resolver) { + return this.resolvers.remove(resolver); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java index 186281ac5e..2e9f678bf8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationRegistrar.java @@ -35,6 +35,7 @@ import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.ManagedSet; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.io.Resource; @@ -46,11 +47,16 @@ import org.springframework.integration.channel.DefaultHeaderChannelRegistry; import org.springframework.integration.config.annotation.MessagingAnnotationPostProcessor; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.context.IntegrationProperties; +import org.springframework.integration.handler.support.CollectionArgumentResolver; +import org.springframework.integration.handler.support.MapArgumentResolver; +import org.springframework.integration.handler.support.PayloadExpressionArgumentResolver; +import org.springframework.integration.handler.support.PayloadsArgumentResolver; import org.springframework.integration.support.DefaultMessageBuilderFactory; import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter; import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter; import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.messaging.converter.CompositeMessageConverter; +import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; import org.springframework.util.ClassUtils; /** @@ -98,6 +104,8 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean registerDefaultConfiguringBeanFactoryPostProcessor(registry); registerDefaultDatatypeChannelMessageConverter(registry); registerArgumentResolverMessageConverter(registry); + registerArgumentResolvers(registry); + registerListCapableArgumentResolvers(registry); if (importingClassMetadata != null) { registerMessagingAnnotationPostProcessors(importingClassMetadata, registry); } @@ -414,14 +422,50 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean * @param registry the registry. */ private void registerArgumentResolverMessageConverter(BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) { - BeanDefinitionBuilder postProcessorBuilder = BeanDefinitionBuilder + if (!registry.containsBeanDefinition( + IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) { + BeanDefinitionBuilder converterBuilder = BeanDefinitionBuilder .genericBeanDefinition(ConfigurableCompositeMessageConverter.class); registry.registerBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME, - postProcessorBuilder.getBeanDefinition()); + converterBuilder.getBeanDefinition()); } } + /** + * Register the default {@link HandlerMethodArgumentResolversHolder} for handler + * method invocation. + * @param registry the registry. + */ + private void registerArgumentResolvers(BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVERS_BEAN_NAME)) { + registry.registerBeanDefinition(IntegrationContextUtils.ARGUMENT_RESOLVERS_BEAN_NAME, + internalArgumentResolversBuilder(registry, false).getBeanDefinition()); + } + } + + /** + * Register the default {@link HandlerMethodArgumentResolversHolder} for handler + * method invocation for lists. + * @param registry the registry. + */ + private void registerListCapableArgumentResolvers(BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition( + IntegrationContextUtils.LIST_ARGUMENT_RESOLVERS_BEAN_NAME)) { + registry.registerBeanDefinition(IntegrationContextUtils.LIST_ARGUMENT_RESOLVERS_BEAN_NAME, + internalArgumentResolversBuilder(registry, true).getBeanDefinition()); + } + } + + private BeanDefinitionBuilder internalArgumentResolversBuilder(BeanDefinitionRegistry registry, + boolean listCapable) { + ManagedList resolvers = new ManagedList<>(); + resolvers.add(new PayloadExpressionArgumentResolver()); + resolvers.add(new PayloadsArgumentResolver()); + resolvers.add(new CollectionArgumentResolver(listCapable)); + resolvers.add(new MapArgumentResolver()); + return BeanDefinitionBuilder.genericBeanDefinition(HandlerMethodArgumentResolversHolder.class) + .addConstructorArgValue(resolvers); + } private void registerMessageBuilderFactory(BeanDefinitionRegistry registry) { boolean alreadyRegistered = false; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java index 2a9db2d3b9..dab26de847 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java @@ -61,18 +61,22 @@ public abstract class IntegrationContextUtils { public static final String MESSAGING_ANNOTATION_POSTPROCESSOR_NAME = IntegrationConfigUtils.BASE_PACKAGE + ".internalMessagingAnnotationPostProcessor"; - public static final String PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME = IntegrationConfigUtils.BASE_PACKAGE + - ".internalPublisherAnnotationBeanPostProcessor"; + public static final String PUBLISHER_ANNOTATION_POSTPROCESSOR_NAME = IntegrationConfigUtils.BASE_PACKAGE + + ".internalPublisherAnnotationBeanPostProcessor"; - public static final String INTEGRATION_CONFIGURATION_POST_PROCESSOR_BEAN_NAME = "IntegrationConfigurationBeanFactoryPostProcessor"; + public static final String INTEGRATION_CONFIGURATION_POST_PROCESSOR_BEAN_NAME = + "IntegrationConfigurationBeanFactoryPostProcessor"; public static final String INTEGRATION_MESSAGE_HISTORY_CONFIGURER_BEAN_NAME = "messageHistoryConfigurer"; - public static final String INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME = "datatypeChannelMessageConverter"; + public static final String INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME = + "datatypeChannelMessageConverter"; - public static final String INTEGRATION_FIXED_SUBSCRIBER_CHANNEL_BPP_BEAN_NAME = "fixedSubscriberChannelBeanFactoryPostProcessor"; + public static final String INTEGRATION_FIXED_SUBSCRIBER_CHANNEL_BPP_BEAN_NAME = + "fixedSubscriberChannelBeanFactoryPostProcessor"; - public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "globalChannelInterceptorProcessor"; + public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = + "globalChannelInterceptorProcessor"; public static final String TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME = "toStringFriendlyJsonNodeToStringConverter"; @@ -84,7 +88,12 @@ public abstract class IntegrationContextUtils { public static final String SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME = "spelPropertyAccessorRegistrar"; - public static final String ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME = "integrationArgumentResolverMessageConverter"; + public static final String ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME = + "integrationArgumentResolverMessageConverter"; + + public static final String ARGUMENT_RESOLVERS_BEAN_NAME = "integrationArgumentResolvers"; + + public static final String LIST_ARGUMENT_RESOLVERS_BEAN_NAME = "integrationListArgumentResolvers"; /** * @param beanFactory BeanFactory for lookup, must not be null. diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/support/CollectionArgumentResolver.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/support/CollectionArgumentResolver.java index 7f28666e7b..04c1358b6e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/support/CollectionArgumentResolver.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/support/CollectionArgumentResolver.java @@ -69,7 +69,8 @@ public class CollectionArgumentResolver extends AbstractExpressionEvaluator if (this.canProcessMessageList) { Assert.state(value instanceof Collection, - "This Argument Resolver only supports messages with a payload of Collection>"); + "This Argument Resolver only supports messages with a payload of Collection>, " + + "payload is: " + value.getClass()); Collection> messages = (Collection>) value; parameter.increaseNestingLevel(); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java index 076b9dc8cd..a44543b9ea 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java @@ -42,6 +42,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.Advised; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanExpressionContext; import org.springframework.beans.factory.config.BeanExpressionResolver; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @@ -69,6 +70,7 @@ import org.springframework.integration.annotation.Default; import org.springframework.integration.annotation.Payloads; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.annotation.UseSpelInvoker; +import org.springframework.integration.config.HandlerMethodArgumentResolversHolder; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.handler.support.CollectionArgumentResolver; import org.springframework.integration.handler.support.MapArgumentResolver; @@ -245,7 +247,8 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator if (beanExpressionResolver != null) { this.resolver = beanExpressionResolver; } - this.expressionContext = new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null); + this.expressionContext = + new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null); } } @@ -257,7 +260,6 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator } } - @SuppressWarnings("unchecked") public T process(Message message) throws Exception { Message messageToProcess = possiblyConvert(message); ParametersWrapper parameters = new ParametersWrapper(messageToProcess); @@ -524,43 +526,74 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator private synchronized void initialize() throws Exception { if (!this.initialized) { - PayloadExpressionArgumentResolver payloadExpressionArgumentResolver = - new PayloadExpressionArgumentResolver(); - payloadExpressionArgumentResolver.setBeanFactory(getBeanFactory()); - - PayloadsArgumentResolver payloadsArgumentResolver = new PayloadsArgumentResolver(); - payloadsArgumentResolver.setBeanFactory(getBeanFactory()); - - CollectionArgumentResolver collectionArgumentResolver = - new CollectionArgumentResolver(this.canProcessMessageList); - collectionArgumentResolver.setBeanFactory(getBeanFactory()); - - MapArgumentResolver mapArgumentResolver = new MapArgumentResolver(); - mapArgumentResolver.setBeanFactory(getBeanFactory()); - - List customArgumentResolvers = new LinkedList<>(); - customArgumentResolvers.add(payloadExpressionArgumentResolver); - customArgumentResolvers.add(payloadsArgumentResolver); - customArgumentResolvers.add(collectionArgumentResolver); - customArgumentResolvers.add(mapArgumentResolver); - - this.messageHandlerMethodFactory.setCustomArgumentResolvers(customArgumentResolvers); - - if (getBeanFactory() != null && - getBeanFactory() - .containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) { - this.messageHandlerMethodFactory - .setMessageConverter(getBeanFactory() - .getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME, - MessageConverter.class)); + if (getBeanFactory() != null + && getBeanFactory().containsBean( + IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) { + try { + this.messageHandlerMethodFactory.setMessageConverter(getBeanFactory().getBean( + IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME, + MessageConverter.class)); + if (this.canProcessMessageList) { + this.messageHandlerMethodFactory.setCustomArgumentResolvers(getBeanFactory().getBean( + IntegrationContextUtils.LIST_ARGUMENT_RESOLVERS_BEAN_NAME, + HandlerMethodArgumentResolversHolder.class).getResolvers()); + } + else { + this.messageHandlerMethodFactory.setCustomArgumentResolvers(getBeanFactory().getBean( + IntegrationContextUtils.ARGUMENT_RESOLVERS_BEAN_NAME, + HandlerMethodArgumentResolversHolder.class).getResolvers()); + } + } + catch (NoSuchBeanDefinitionException e) { + configureLocalMessageHandlerFactory(); + } + } + else { + configureLocalMessageHandlerFactory(); } - this.messageHandlerMethodFactory.afterPropertiesSet(); prepareEvaluationContext(); this.initialized = true; } } + /* + * This should not be needed in production but we have many tests + * that don't run in an application context. + */ + private void configureLocalMessageHandlerFactory() { + PayloadExpressionArgumentResolver payloadExpressionArgumentResolver = + new PayloadExpressionArgumentResolver(); + payloadExpressionArgumentResolver.setBeanFactory(getBeanFactory()); + + PayloadsArgumentResolver payloadsArgumentResolver = new PayloadsArgumentResolver(); + payloadsArgumentResolver.setBeanFactory(getBeanFactory()); + + CollectionArgumentResolver collectionArgumentResolver = + new CollectionArgumentResolver(this.canProcessMessageList); + collectionArgumentResolver.setBeanFactory(getBeanFactory()); + + MapArgumentResolver mapArgumentResolver = new MapArgumentResolver(); + mapArgumentResolver.setBeanFactory(getBeanFactory()); + + List customArgumentResolvers = new LinkedList<>(); + customArgumentResolvers.add(payloadExpressionArgumentResolver); + customArgumentResolvers.add(payloadsArgumentResolver); + customArgumentResolvers.add(collectionArgumentResolver); + customArgumentResolvers.add(mapArgumentResolver); + + this.messageHandlerMethodFactory.setCustomArgumentResolvers(customArgumentResolvers); + + if (getBeanFactory() != null && + getBeanFactory() + .containsBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)) { + this.messageHandlerMethodFactory + .setMessageConverter(getBeanFactory() + .getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME, + MessageConverter.class)); + } + } + @SuppressWarnings("unchecked") private T invokeHandlerMethod(HandlerMethod handlerMethod, ParametersWrapper parameters) throws Exception { try {