From 969310b453deb1ebaa912a08e93fc299a7680c02 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 31 Mar 2014 16:38:28 +0300 Subject: [PATCH] INT-3327: Add `IntegrationConverter` Annotation JIRA: https://jira.spring.io/browse/INT-3327 INT-3327: Addressing PR comments Polishing --- .../config/GlobalChannelInterceptor.java | 4 +- .../config/IntegrationConverter.java | 42 +++++++++ .../IntegrationConverterInitializer.java | 93 +++++++++++++++++++ .../config/xml/ConverterParser.java | 35 ++----- .../context/ConversionServiceCreator.java | 72 -------------- .../context/ConverterRegistrar.java | 9 +- .../CustomConversionServiceFactoryBean.java | 42 +++++++++ .../context/IntegrationContextUtils.java | 2 + .../main/resources/META-INF/spring.factories | 3 +- .../configuration/EnableIntegrationTests.java | 63 +++++++++++++ .../MapToObjectTransformerTests.java | 38 ++------ src/reference/docbook/endpoint.xml | 31 ++++++- src/reference/docbook/overview.xml | 9 ++ src/reference/docbook/whats-new.xml | 12 ++- 14 files changed, 317 insertions(+), 138 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverter.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java delete mode 100644 spring-integration-core/src/main/java/org/springframework/integration/context/ConversionServiceCreator.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/context/CustomConversionServiceFactoryBean.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java index fa2a8ce81c..323a8c0cf4 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java @@ -27,8 +27,10 @@ import java.lang.annotation.Target; * annotation will be applied as global channel interceptors * using the provided {@code patterns} to match channel names. *

- * The annotation can be used at the {@code class} level for {@link org.springframework.stereotype.Component} beans + * This annotation can be used at the {@code class} level for {@link org.springframework.stereotype.Component} beans * and on methods with {@link org.springframework.context.annotation.Bean}. + *

+ * This annotation is an analogue of {@code }. * * @author Artem Bilan * @since 4.0 diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverter.java new file mode 100644 index 0000000000..5e68156bfc --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A marker annotation (an analogue of {@code }) to register + * {@link org.springframework.core.convert.converter.Converter}, + * {@link org.springframework.core.convert.converter.GenericConverter} or + * {@link org.springframework.core.convert.converter.ConverterFactory} beans for the {@code integrationConversionService}. + *

+ * This annotation can be used at the {@code class} level for {@link org.springframework.stereotype.Component} beans + * and on methods with {@link org.springframework.context.annotation.Bean}. + * + * @author Artem Bilan + * @since 4.0 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface IntegrationConverter { + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java new file mode 100644 index 0000000000..874dfa14fd --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConverterInitializer.java @@ -0,0 +1,93 @@ +/* + * Copyright 2014 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.Set; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.ManagedSet; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.integration.context.IntegrationContextUtils; + +/** + * @author Artem Bilan + * @since 4.0 + */ +public class IntegrationConverterInitializer implements IntegrationConfigurationInitializer { + + private static final String CONTEXT_PACKAGE = "org.springframework.integration.context."; + + @Override + public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException { + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + + for (String beanName : registry.getBeanDefinitionNames()) { + BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); + if (beanDefinition instanceof AnnotatedBeanDefinition) { + AnnotationMetadata metadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata(); + boolean hasIntegrationConverter = metadata.hasAnnotation(IntegrationConverter.class.getName()); + + if (!hasIntegrationConverter && beanDefinition.getSource() instanceof MethodMetadata) { + MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource(); + hasIntegrationConverter = beanMethod.isAnnotated(IntegrationConverter.class.getName()); + } + + if (hasIntegrationConverter) { + this.registerConverter(registry, new RuntimeBeanReference(beanName)); + } + } + } + } + + @SuppressWarnings("unchecked") + public void registerConverter(BeanDefinitionRegistry registry, BeanMetadataElement converterBeanDefinition) { + Set converters = new ManagedSet(); + if (!registry.containsBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME)) { + BeanDefinitionBuilder converterRegistrarBuilder = BeanDefinitionBuilder.genericBeanDefinition( + CONTEXT_PACKAGE + "ConverterRegistrar").addConstructorArgValue(converters); + registry.registerBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME, + converterRegistrarBuilder.getBeanDefinition()); + + if (!registry.containsBeanDefinition(IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME)) { + registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, + new RootBeanDefinition(CONTEXT_PACKAGE + "CustomConversionServiceFactoryBean")); + } + } + else { + BeanDefinition converterRegistrarBeanDefinition = registry + .getBeanDefinition(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME); + converters = (Set) converterRegistrarBeanDefinition + .getConstructorArgumentValues() + .getIndexedArgumentValues() + .values() + .iterator() + .next() + .getValue(); + } + + converters.add(converterBeanDefinition); + } +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java index cfa6abced6..e438fa1468 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ConverterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2014 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. @@ -21,56 +21,37 @@ import org.w3c.dom.Element; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.ManagedSet; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.IntegrationConverterInitializer; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * @author Oleg Zhurakousky * @author Mark Fisher + * @author Artem Bilan * @since 2.0 */ public class ConverterParser extends AbstractBeanDefinitionParser { - private final ManagedSet converters = new ManagedSet(); - - private volatile boolean initialized; - - private final Object initializationMonitor = new Object(); - + private final static IntegrationConverterInitializer INTEGRATION_CONVERTER_INITIALIZER = new IntegrationConverterInitializer(); @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - this.initializeConversionServiceInfrastructureIfNecessary(parserContext); + BeanDefinitionRegistry registry = parserContext.getRegistry(); BeanComponentDefinition converterDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext); if (converterDefinition != null) { - this.converters.add(converterDefinition); + INTEGRATION_CONVERTER_INITIALIZER.registerConverter(registry, converterDefinition); } else { String beanName = element.getAttribute("ref"); Assert.isTrue(StringUtils.hasText(beanName), "Either a 'ref' attribute pointing to a Converter or a sub-element defining a Converter is required."); - this.converters.add(new RuntimeBeanReference(beanName)); + INTEGRATION_CONVERTER_INITIALIZER.registerConverter(registry, new RuntimeBeanReference(beanName)); } return null; } - private void initializeConversionServiceInfrastructureIfNecessary(ParserContext parserContext) { - synchronized (this.initializationMonitor) { - if (!this.initialized) { - String contextPackage = "org.springframework.integration.context."; - BeanDefinitionBuilder creatorBuilder = BeanDefinitionBuilder.rootBeanDefinition(contextPackage + "ConversionServiceCreator"); - BeanDefinitionReaderUtils.registerWithGeneratedName(creatorBuilder.getBeanDefinition(), parserContext.getRegistry()); - BeanDefinitionBuilder conversionServiceBuilder = BeanDefinitionBuilder.rootBeanDefinition(contextPackage + "ConverterRegistrar"); - conversionServiceBuilder.addConstructorArgValue(converters); - BeanDefinitionReaderUtils.registerWithGeneratedName(conversionServiceBuilder.getBeanDefinition(), parserContext.getRegistry()); - this.initialized = true; - } - } - } - } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/ConversionServiceCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/context/ConversionServiceCreator.java deleted file mode 100644 index 0abd0fce41..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/context/ConversionServiceCreator.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2010 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.context; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.context.support.ConversionServiceFactoryBean; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.GenericConversionService; - -/** - * @author Oleg Zhurakousky - * @author Mark Fisher - * @since 2.0 - */ -class ConversionServiceCreator implements BeanFactoryPostProcessor { - - private final Log logger = LogFactory.getLog(this.getClass()); - - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - if (!beanFactory.containsBean(IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME)) { - if (beanFactory instanceof BeanDefinitionRegistry) { - BeanDefinitionBuilder conversionServiceBuilder = BeanDefinitionBuilder.rootBeanDefinition(CustomConversionServiceFactoryBean.class); - BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder( - conversionServiceBuilder.getBeanDefinition(), IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME); - BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, (BeanDefinitionRegistry) beanFactory); - } - else if (logger.isWarnEnabled()) { - logger.warn("BeanFactory is not a BeanDefinitionRegistry implementation. Cannot register a default ConversionService."); - } - } - } - - - /** - * This is a workaround until we depend on Spring 3.1 and specifically when SPR-8818 is resolved. - * See INT-2259 and INT-1893 for more detail. - */ - static class CustomConversionServiceFactoryBean extends ConversionServiceFactoryBean { - - @Override - public ConversionService getObject() { - ConversionService service = super.getObject(); - if (service instanceof GenericConversionService) { - ((GenericConversionService) service).removeConvertible(Object.class, Object.class); - } - return service; - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/ConverterRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/context/ConverterRegistrar.java index ba5e8f201f..656685a69e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/context/ConverterRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/ConverterRegistrar.java @@ -22,7 +22,6 @@ 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.converter.Converter; import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.util.Assert; @@ -30,19 +29,19 @@ import org.springframework.util.Assert; /** * Utility class that keeps track of a set of Converters in order to register * them with the "integrationConversionService" upon initialization. - * + * * @author Oleg Zhurakousky * @author Mark Fisher * @since 2.0 */ class ConverterRegistrar implements InitializingBean, BeanFactoryAware { - private final Set> converters; + private final Set converters; private BeanFactory beanFactory; - public ConverterRegistrar(Set> converters) { + public ConverterRegistrar(Set converters) { this.converters = converters; } @@ -55,7 +54,7 @@ class ConverterRegistrar implements InitializingBean, BeanFactoryAware { Assert.notNull(beanFactory, "BeanFactory is required"); ConversionService conversionService = IntegrationContextUtils.getConversionService(beanFactory); if (conversionService instanceof GenericConversionService) { - ConversionServiceFactory.registerConverters(converters, (GenericConversionService) conversionService); + ConversionServiceFactory.registerConverters(converters, (GenericConversionService) conversionService); } else { Assert.notNull(conversionService, "Failed to locate '" + IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME + "'"); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/CustomConversionServiceFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/context/CustomConversionServiceFactoryBean.java new file mode 100644 index 0000000000..84f71a1dbf --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/CustomConversionServiceFactoryBean.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 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.context; + +import org.springframework.context.support.ConversionServiceFactoryBean; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.GenericConversionService; + +/** + * This is a workaround until SPR-8818 will be resolved. + * See INT-2259 and INT-1893 for more detail. + * + * @author Oleg Zhurakousky + * @author Mark Fisher + * @since 2.0 + */ +class CustomConversionServiceFactoryBean extends ConversionServiceFactoryBean { + + @Override + public ConversionService getObject() { + ConversionService service = super.getObject(); + if (service instanceof GenericConversionService) { + ((GenericConversionService) service).removeConvertible(Object.class, Object.class); + } + return service; + } + +} 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 48ef2a27f2..c703b39c60 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 @@ -52,6 +52,8 @@ public abstract class IntegrationContextUtils { public static final String METADATA_STORE_BEAN_NAME = "metadataStore"; + public static final String CONVERTER_REGISTRAR_BEAN_NAME = "converterRegistrar"; + public static final String INTEGRATION_CONVERSION_SERVICE_BEAN_NAME = "integrationConversionService"; public static final String INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME = "integrationEvaluationContext"; diff --git a/spring-integration-core/src/main/resources/META-INF/spring.factories b/spring-integration-core/src/main/resources/META-INF/spring.factories index b8ac0151ab..5ace75426a 100644 --- a/spring-integration-core/src/main/resources/META-INF/spring.factories +++ b/spring-integration-core/src/main/resources/META-INF/spring.factories @@ -1,4 +1,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.integration.config.boot.IntegrationAutoConfiguration org.springframework.integration.config.IntegrationConfigurationInitializer=\ -org.springframework.integration.config.GlobalChannelInterceptorInitializer +org.springframework.integration.config.GlobalChannelInterceptorInitializer,\ +org.springframework.integration.config.IntegrationConverterInitializer diff --git a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java index 67439cd56c..728d4920c0 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.java @@ -42,6 +42,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.integration.annotation.Gateway; import org.springframework.integration.annotation.GatewayHeader; import org.springframework.integration.annotation.IntegrationComponentScan; @@ -53,6 +55,7 @@ import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.annotation.Transformer; import org.springframework.integration.channel.AbstractMessageChannel; import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.FixedSubscriberChannel; import org.springframework.integration.channel.NullChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.channel.interceptor.WireTap; @@ -60,8 +63,10 @@ import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.config.EnableMessageHistory; import org.springframework.integration.config.EnablePublisher; import org.springframework.integration.config.GlobalChannelInterceptor; +import org.springframework.integration.config.IntegrationConverter; import org.springframework.integration.history.MessageHistory; import org.springframework.integration.history.MessageHistoryConfigurer; +import org.springframework.integration.message.MutableMessage; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; @@ -69,6 +74,7 @@ import org.springframework.messaging.MessageChannel; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.messaging.support.ChannelInterceptorAdapter; +import org.springframework.messaging.support.GenericMessage; import org.springframework.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; @@ -110,6 +116,15 @@ public class EnableIntegrationTests { @Autowired private AtomicInteger fbInterceptorCounter; + @Autowired + private MessageChannel numberChannel; + + @Autowired + private TestConverter testConverter; + + @Autowired + private MessageChannel bytesChannel; + @Test public void testAnnotatedServiceActivator() { this.input.send(MessageBuilder.withPayload("Foo").build()); @@ -192,6 +207,17 @@ public class EnableIntegrationTests { child.close(); } + @Test + public void testIntegrationConverter() { + this.numberChannel.send(new GenericMessage(10)); + this.numberChannel.send(new GenericMessage(true)); + assertThat(this.testConverter.getInvoked(), Matchers.greaterThan(0)); + + assertTrue(this.bytesChannel.send(new GenericMessage("foo".getBytes()))); + assertTrue(this.bytesChannel.send(new GenericMessage>(new MutableMessage("")))); + + } + @Configuration @ComponentScan @IntegrationComponentScan @@ -271,6 +297,23 @@ public class EnableIntegrationTests { } + @Component + @IntegrationConverter + public static class TestConverter implements Converter { + + private final AtomicInteger invoked = new AtomicInteger(); + + @Override + public Number convert(Boolean source) { + this.invoked.incrementAndGet(); + return source ? 1 : 0; + } + + public Integer getInvoked() { + return invoked.get(); + } + } + @Configuration @EnableIntegration @ImportResource("classpath:org/springframework/integration/configuration/EnableIntegrationTests-context.xml") @@ -293,6 +336,26 @@ public class EnableIntegrationTests { return new DirectChannel(); } + @Bean + public QueueChannel numberChannel() { + QueueChannel channel = new QueueChannel(); + channel.setDatatypes(Number.class); + return channel; + } + + @Bean + public QueueChannel bytesChannel() { + QueueChannel channel = new QueueChannel(); + channel.setDatatypes(byte[].class); + return channel; + } + + @Bean + @IntegrationConverter + public SerializingConverter serializingConverter() { + return new SerializingConverter(); + } + } @Configuration diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/MapToObjectTransformerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/transformer/MapToObjectTransformerTests.java index 30a900be46..8a9250565d 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/transformer/MapToObjectTransformerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/MapToObjectTransformerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -16,29 +16,25 @@ package org.springframework.integration.transformer; -import java.lang.reflect.Constructor; -import java.security.AccessController; -import java.security.PrivilegedExceptionAction; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + import java.util.HashMap; import java.util.Map; import org.junit.Test; -import org.springframework.beans.BeanUtils; + import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterRegistry; -import org.springframework.messaging.Message; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; -import org.springframework.util.ClassUtils; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import org.springframework.messaging.Message; /** @@ -123,22 +119,8 @@ public class MapToObjectTransformerTests { private BeanFactory getBeanFactory() { GenericApplicationContext ctx = TestUtils.createTestApplicationContext(); - Constructor constructorToUse = null; - try { - // Add the integrationConversionService (reflection needed because of package protection) - final Class conversionServiceCreatorClass = ClassUtils.forName("org.springframework.integration.context.ConversionServiceCreator", - ClassUtils.getDefaultClassLoader()); - constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction>() { - public Constructor run() throws Exception { - return conversionServiceCreatorClass.getDeclaredConstructor((Class[]) null); - } - }); - } - catch (Exception e) { - throw new RuntimeException("Unexpected Privilege Exception: ", e); - } - - ctx.addBeanFactoryPostProcessor((BeanFactoryPostProcessor) BeanUtils.instantiateClass(constructorToUse)); + ctx.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_CONVERSION_SERVICE_BEAN_NAME, + new RootBeanDefinition("org.springframework.integration.context.CustomConversionServiceFactoryBean")); ctx.refresh(); return ctx; } diff --git a/src/reference/docbook/endpoint.xml b/src/reference/docbook/endpoint.xml index 23ffebf694..b66dd5682b 100644 --- a/src/reference/docbook/endpoint.xml +++ b/src/reference/docbook/endpoint.xml @@ -588,10 +588,12 @@ any transaction configuration essentially allowing you to enhance the behavior o In this scenario we need to perform type conversion. Spring Integration provides a convenient way for registering type converters (using the Spring 3.x ConversionService) within its own instance of a conversion service bean named integrationConversionService. - That bean is automatically created as soon as the first converter is defined using the Spring Integration namespace support. + That bean is automatically created as soon as the first converter is defined using the Spring Integration infrastructure. To register a Converter all you need is to implement - org.springframework.core.convert.converter.Converter and define it via + org.springframework.core.convert.converter.Converter, + org.springframework.core.convert.converter.GenericConverter or + org.springframework.core.convert.converter.ConverterFactory and define it via convenient namespace support: @@ -602,6 +604,31 @@ any transaction configuration essentially allowing you to enhance the behavior o ]]> + + Starting with Spring Integration 4.0, the above configuration is available using annotations: + { + + public Number convert(Boolean source) { + return source ? 1 : 0; + } + +}]]> + + or as a @Configuration part: + + When configuring an Application Context, the diff --git a/src/reference/docbook/overview.xml b/src/reference/docbook/overview.xml index 08383b64e7..72ab123c7d 100644 --- a/src/reference/docbook/overview.xml +++ b/src/reference/docbook/overview.xml @@ -368,6 +368,15 @@ on @Bean methods within @Configuration classes. In either case, the bean must be a ChannelInterceptor. + + The @IntegrationConverter annotation has been introduced to + mark Converter, GenericConverter or + ConverterFactory beans as candidate converters for integrationConversionService. + This annotation is an analogue of the <int:converter> xml element + (see ). @IntegrationConverter annotations + can be placed at the class level (with a @Component stereotype annotation), or + on @Bean methods within @Configuration classes. + diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 7b04c9cd24..4c8aabc186 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -56,7 +56,7 @@ @MessagingGateway Messaging gateway interfaces can now be configured with the @MessagingGateway annotation. - It is an analogue of the <gateway/> xml element. + It is an analogue of the <int:gateway/> xml element. For more information, see . @@ -68,7 +68,7 @@ Spring Integration infrastructure beans to be configured using Spring Boot's @EnableAutoConfiguration. For more information see - Spring Boot - AutoConfigure. @@ -80,6 +80,14 @@ For more information, see . +
+ @IntegrationConverter + + The @IntegrationConverter annotation has bean introduced, + as an analogue of <int:converter/> component. + For more information, see . + +
@EnablePublisher