From 9272782a4835a931fa7abc3cf33a734cb018284e Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Fri, 13 Sep 2019 12:03:00 -0400 Subject: [PATCH] GH-3052: Fix custom converters for lambdas Fixes https://github.com/spring-projects/spring-integration/issues/3052 Starting with version `5.1`, a `LambdaMessageProcessor` is based on the `MessageConverter` instead of plain `ConversionService`. (A `ConfigurableCompositeMessageConverter` is used from the application context.) However a type conversion based on the `@IntegrationConverter` is lost in the `GenericMessageConverter` because it doesn't use an `integrationConversionService` from the application context * Change `ConfigurableCompositeMessageConverter` to configure a `GenericMessageConverter` with an `integrationConversionService` if any * Fix `MessagingMethodInvokerHelper` to populate a `BeanFactory` into its internal `ConfigurableCompositeMessageConverter` * Ensure in the `LambdaMessageProcessorTests` that `@IntegrationConverter` is applied for ``LambdaMessageProcessor` as well **Cherry-pick to 5.1.x** --- .../support/MessagingMethodInvokerHelper.java | 18 ++-- ...ConfigurableCompositeMessageConverter.java | 34 +++++++- .../dsl/LambdaMessageProcessorTests.java | 84 +++++++++++++++---- 3 files changed, 109 insertions(+), 27 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java index dc48004a83..2c240af505 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/support/MessagingMethodInvokerHelper.java @@ -86,7 +86,6 @@ import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.MessageConversionException; -import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Headers; import org.springframework.messaging.handler.annotation.Payload; @@ -541,7 +540,9 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator im private void configureLocalMessageHandlerFactory() { BeanFactory beanFactory = getBeanFactory(); - MessageConverter messageConverter = new ConfigurableCompositeMessageConverter(); + ConfigurableCompositeMessageConverter messageConverter = new ConfigurableCompositeMessageConverter(); + messageConverter.setBeanFactory(beanFactory); + messageConverter.afterPropertiesSet(); List customArgumentResolvers = new LinkedList<>(); PayloadExpressionArgumentResolver payloadExpressionArgumentResolver = new PayloadExpressionArgumentResolver(); @@ -560,14 +561,11 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator im MapArgumentResolver mapArgumentResolver = new MapArgumentResolver(); customArgumentResolvers.add(mapArgumentResolver); - - if (beanFactory != null) { - payloadExpressionArgumentResolver.setBeanFactory(beanFactory); - payloadsArgumentResolver.setBeanFactory(beanFactory); - mapArgumentResolver.setBeanFactory(beanFactory); - if (collectionArgumentResolver != null) { - collectionArgumentResolver.setBeanFactory(beanFactory); - } + payloadExpressionArgumentResolver.setBeanFactory(beanFactory); + payloadsArgumentResolver.setBeanFactory(beanFactory); + mapArgumentResolver.setBeanFactory(beanFactory); + if (collectionArgumentResolver != null) { + collectionArgumentResolver.setBeanFactory(beanFactory); } DefaultMessageHandlerMethodFactory localHandlerMethodFactory = diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java index 81b16f04f7..e250903727 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java @@ -22,8 +22,15 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.integration.support.json.Jackson2JsonObjectMapper; import org.springframework.integration.support.json.JacksonPresent; +import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.messaging.converter.ByteArrayMessageConverter; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.GenericMessageConverter; @@ -47,13 +54,19 @@ import org.springframework.messaging.converter.MessageConverter; * * @since 5.0 */ -public class ConfigurableCompositeMessageConverter extends CompositeMessageConverter { +public class ConfigurableCompositeMessageConverter extends CompositeMessageConverter + implements BeanFactoryAware, InitializingBean { + + private final boolean registerDefaults; + + private BeanFactory beanFactory; /** * Create an instance with the default converters. */ public ConfigurableCompositeMessageConverter() { super(initDefaults()); + this.registerDefaults = true; } /** @@ -73,6 +86,23 @@ public class ConfigurableCompositeMessageConverter extends CompositeMessageConve super(registerDefaults ? Stream.concat(converters.stream(), initDefaults().stream()).collect(Collectors.toList()) : converters); + this.registerDefaults = registerDefaults; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public void afterPropertiesSet() { + if (this.registerDefaults) { + ConversionService conversionService = IntegrationUtils.getConversionService(this.beanFactory); + if (conversionService == null) { + conversionService = DefaultConversionService.getSharedInstance(); + } + getConverters().add(new GenericMessageConverter(conversionService)); + } } private static Collection initDefaults() { @@ -90,8 +120,6 @@ public class ConfigurableCompositeMessageConverter extends CompositeMessageConve // TODO do we port it together with MessageConverterUtils ? // converters.add(new JavaSerializationMessageConverter()); - converters.add(new GenericMessageConverter()); - return converters; } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java index eecd013265..86d12ae1ce 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/dsl/LambdaMessageProcessorTests.java @@ -19,20 +19,26 @@ package org.springframework.integration.dsl; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.fail; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; + +import java.util.Objects; +import java.util.function.Function; import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.beans.factory.BeanFactory; -import org.springframework.integration.context.IntegrationContextUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.config.IntegrationConverter; import org.springframework.integration.handler.GenericHandler; import org.springframework.integration.handler.LambdaMessageProcessor; -import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter; import org.springframework.integration.transformer.GenericTransformer; import org.springframework.messaging.Message; -import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.context.junit4.SpringRunner; /** @@ -41,8 +47,12 @@ import org.springframework.messaging.support.GenericMessage; * * @since 5.0 */ +@RunWith(SpringRunner.class) public class LambdaMessageProcessorTests { + @Autowired + private BeanFactory beanFactory; + @Test @SuppressWarnings("divzero") public void testException() { @@ -66,7 +76,7 @@ public class LambdaMessageProcessorTests { } }, null); - lmp.setBeanFactory(mock(BeanFactory.class)); + lmp.setBeanFactory(this.beanFactory); GenericMessage testMessage = new GenericMessage<>("foo"); Object result = lmp.processMessage(testMessage); assertThat(result).isSameAs(testMessage); @@ -76,16 +86,24 @@ public class LambdaMessageProcessorTests { public void testMessageAsArgumentLambda() { LambdaMessageProcessor lmp = new LambdaMessageProcessor( (GenericTransformer, Message>) this::messageTransformer, null); - lmp.setBeanFactory(mock(BeanFactory.class)); + lmp.setBeanFactory(this.beanFactory); GenericMessage testMessage = new GenericMessage<>("foo"); assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(() -> lmp.processMessage(testMessage)) .withCauseInstanceOf(ClassCastException.class); } + @Test + public void testCustomConverter() { + LambdaMessageProcessor lmp = new LambdaMessageProcessor(Function.identity(), TestPojo.class); + lmp.setBeanFactory(this.beanFactory); + Object result = lmp.processMessage(new GenericMessage<>("foo")); + assertThat(result).isEqualTo(new TestPojo("foo")); + } + private void handle(GenericHandler h) { LambdaMessageProcessor lmp = new LambdaMessageProcessor(h, String.class); - lmp.setBeanFactory(getBeanFactory()); + lmp.setBeanFactory(this.beanFactory); lmp.processMessage(new GenericMessage<>("foo")); } @@ -95,12 +113,50 @@ public class LambdaMessageProcessorTests { } - private BeanFactory getBeanFactory() { - BeanFactory mockBeanFactory = mock(BeanFactory.class); - given(mockBeanFactory.getBean(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME, - MessageConverter.class)) - .willReturn(new ConfigurableCompositeMessageConverter()); - return mockBeanFactory; + @Configuration + @EnableIntegration + public static class TestConfiguration { + + @Bean + @IntegrationConverter + public Converter testPojoConverter() { + return new Converter() { // Cannot be lambda for explicit generic types + + @Override + public TestPojo convert(String source) { + return new TestPojo(source); + } + + }; + } + + } + + private static class TestPojo { + + private final String value; + + TestPojo(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TestPojo)) { + return false; + } + TestPojo testPojo = (TestPojo) o; + return Objects.equals(this.value, testPojo.value); + } + + @Override + public int hashCode() { + return Objects.hash(this.value); + } + } }