From 6d6bef58b202b84e20b157e57bbc6004365e15b5 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 17 Mar 2014 16:59:16 +0200 Subject: [PATCH] INT-3326 Add `GlobalChannelInterceptor` Annotation JIRA: https://jira.spring.io/browse/INT-3326 INT-3326: Add parent-child test INT-3326: Fix `ClassCastException` for `GCII` INT-3326: Rebase and Polishing INT-3326: Fix `GCII` for `getBeanNamesForType` INT-3326: Add `GlobalChannelInterceptor` annotation JIRA: https://jira.spring.io/browse/INT-3326 INT-3326: Add parent-child test INT-3326: Fix `ClassCastException` for `GCII` INT-3326: Rebase and Polishing INT-3326: Fix `GCII` for `getBeanNamesForType` INT-3326 Polishing Doc, JavaDocs Also fix a typo in the bean name for the CI processor. INT-3326: Polishing Doc Polishing --- .../config/GlobalChannelInterceptor.java | 56 +++++++ .../GlobalChannelInterceptorInitializer.java | 71 +++++++++ .../GlobalChannelInterceptorProcessor.java | 4 +- .../config/IntegrationRegistrar.java | 15 ++ .../xml/GlobalChannelInterceptorParser.java | 38 ++--- .../context/IntegrationContextUtils.java | 2 +- .../main/resources/META-INF/spring.factories | 2 + .../EnableIntegrationTests-context.xml | 10 +- .../configuration/EnableIntegrationTests.java | 150 +++++++++++++++++- .../configuration2/ChildConfiguration.java | 52 ++++++ src/reference/docbook/overview.xml | 9 ++ src/reference/docbook/whats-new.xml | 8 + 12 files changed, 381 insertions(+), 36 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java rename spring-integration-core/src/main/java/org/springframework/integration/{channel/interceptor => config}/GlobalChannelInterceptorProcessor.java (96%) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/configuration2/ChildConfiguration.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 new file mode 100644 index 0000000000..fa2a8ce81c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java @@ -0,0 +1,56 @@ +/* + * 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; + +/** + * {@link org.springframework.messaging.support.ChannelInterceptor} components with this + * 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 + * 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 GlobalChannelInterceptor { + + /** + * An array of simple patterns against which channel names will be matched. Default is "*" + * (all channels). See {@link org.springframework.util.PatternMatchUtils#simpleMatch(String, String)}. + * @return The pattern. + */ + String[] patterns() default "*"; + + /** + * The order of the interceptor. Interceptors with negative order values will be placed before any + * explicit interceptors on the channel; interceptors with positive order values will be + * placed after explicit interceptors. + * @return The order. + */ + int order() default 0; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java new file mode 100644 index 0000000000..9ff505615c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorInitializer.java @@ -0,0 +1,71 @@ +/* + * 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.Map; + +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.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.util.CollectionUtils; + +/** + * The {@link IntegrationConfigurationInitializer} to populate {@link GlobalChannelInterceptorWrapper} + * for {@link ChannelInterceptor}s marked with {@link GlobalChannelInterceptor} annotation. + *

+ * {@link org.springframework.context.annotation.Bean} methods are also processed. + * + * @author Artem Bilan + * @since 4.0 + */ +public class GlobalChannelInterceptorInitializer implements IntegrationConfigurationInitializer { + + @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(); + Map annotationAttributes = metadata.getAnnotationAttributes(GlobalChannelInterceptor.class.getName()); + if (CollectionUtils.isEmpty(annotationAttributes) && beanDefinition.getSource() instanceof MethodMetadata) { + MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource(); + annotationAttributes = beanMethod.getAnnotationAttributes(GlobalChannelInterceptor.class.getName()); + } + + if (!CollectionUtils.isEmpty(annotationAttributes)) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class) + .addConstructorArgReference(beanName) + .addPropertyValue("patterns", annotationAttributes.get("patterns")) + .addPropertyValue("order", annotationAttributes.get("order")); + + BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); + } + } + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java similarity index 96% rename from spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorProcessor.java rename to spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java index efbdaaddeb..9410aa0dcf 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.channel.interceptor; +package org.springframework.integration.config; import java.util.ArrayList; import java.util.Collection; @@ -35,6 +35,8 @@ import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.context.SmartLifecycle; import org.springframework.core.OrderComparator; import org.springframework.integration.channel.ChannelInterceptorAware; +import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; +import org.springframework.integration.channel.interceptor.VetoCapableInterceptor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; 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 155f2a49d1..8741e141f3 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 @@ -84,6 +84,7 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean this.registerIntegrationEvaluationContext(registry); this.registerIntegrationProperties(registry); this.registerHeaderChannelRegistry(registry); + this.registerGlobalChannelInterceptorProcessor(registry); this.registerBuiltInBeans(registry); this.registerDefaultConfiguringBeanFactoryPostProcessor(registry); this.registerDefaultDatatypeChannelMessageConverter(registry); @@ -285,6 +286,20 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean } } + /** + * Register a {@link GlobalChannelInterceptorProcessor} in the given {@link BeanDefinitionRegistry}, if necessary. + * + * @param registry The {@link BeanDefinitionRegistry} to register additional {@link org.springframework.beans.factory.config.BeanDefinition}s. + */ + private void registerGlobalChannelInterceptorProcessor(BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition(IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME)) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorProcessor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + + registry.registerBeanDefinition(IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME, builder.getBeanDefinition()); + } + } + /** * Register {@link MessagingAnnotationPostProcessor} and {@link PublisherAnnotationBeanPostProcessor}, if necessary. * Inject {@code defaultPublishedChannel} from provided {@link AnnotationMetadata}, if any. diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GlobalChannelInterceptorParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GlobalChannelInterceptorParser.java index 6ac44b532f..ba92cc4c85 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GlobalChannelInterceptorParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GlobalChannelInterceptorParser.java @@ -26,12 +26,9 @@ 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.ManagedList; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; -import org.springframework.integration.config.IntegrationConfigUtils; -import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.util.xml.DomUtils; /** @@ -41,46 +38,33 @@ import org.springframework.util.xml.DomUtils; * @author Mark Fisher * @author David Turanski * @author Gary Russell + * @author Artem Bilan * @since 2.0 */ public class GlobalChannelInterceptorParser extends AbstractBeanDefinitionParser { - private static final String BASE_PACKAGE = IntegrationConfigUtils.BASE_PACKAGE + ".channel.interceptor."; - private static final String CHANNEL_NAME_PATTERN_ATTRIBUTE = "pattern"; private static final String REF_ATTRIBUTE = "ref"; - private static final String GLOBAL_INTERCEPTOR_PROCESSOR_CLASSNAME = "GlobalChannelInterceptorProcessor"; - - - private final ManagedList globalInterceptors = new ManagedList(); + @Override + protected boolean shouldGenerateId() { + return true; + } + @Override + protected boolean shouldFireEvents() { + return false; + } @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - this.createAndRegisterGlobalPostProcessorIfNecessary(parserContext); BeanDefinitionBuilder globalChannelInterceptorBuilder = BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class); Object childBeanDefinition = getBeanDefinitionBuilderConstructorValue(element, parserContext); globalChannelInterceptorBuilder.addConstructorArgValue(childBeanDefinition); IntegrationNamespaceUtils.setValueIfAttributeDefined(globalChannelInterceptorBuilder, element, "order"); - IntegrationNamespaceUtils.setValueIfAttributeDefined( - globalChannelInterceptorBuilder, element, CHANNEL_NAME_PATTERN_ATTRIBUTE, "patterns"); - String beanName = BeanDefinitionReaderUtils.generateBeanName( - globalChannelInterceptorBuilder.getBeanDefinition(), parserContext.getRegistry()); - parserContext.registerBeanComponent(new BeanComponentDefinition(globalChannelInterceptorBuilder.getBeanDefinition(), beanName)); - this.globalInterceptors.add(new RuntimeBeanReference(beanName)); - return null; - } - - private void createAndRegisterGlobalPostProcessorIfNecessary(ParserContext parserContext) { - if (!parserContext.getRegistry().containsBeanDefinition(IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME)) { - BeanDefinitionBuilder processorBuilder = BeanDefinitionBuilder.genericBeanDefinition( - BASE_PACKAGE + GLOBAL_INTERCEPTOR_PROCESSOR_CLASSNAME); - BeanDefinition beanDef = processorBuilder.getBeanDefinition(); - parserContext.registerBeanComponent(new BeanComponentDefinition(beanDef, - IntegrationContextUtils.GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME)); - } + IntegrationNamespaceUtils.setValueIfAttributeDefined(globalChannelInterceptorBuilder, element, CHANNEL_NAME_PATTERN_ATTRIBUTE, "patterns"); + return globalChannelInterceptorBuilder.getBeanDefinition(); } protected Object getBeanDefinitionBuilderConstructorValue(Element element, ParserContext parserContext) { 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 2e02516f5a..48ef2a27f2 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 @@ -82,7 +82,7 @@ public abstract class IntegrationContextUtils { public static final String INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME = "messageBuilderFactory"; - public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "gloabelChannelInterceptorProcessor"; + public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "globalChannelInterceptorProcessor"; /** * @param beanFactory BeanFactory for lookup, must not be null. 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 48ce58c12c..b8ac0151ab 100644 --- a/spring-integration-core/src/main/resources/META-INF/spring.factories +++ b/spring-integration-core/src/main/resources/META-INF/spring.factories @@ -1,2 +1,4 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.integration.config.boot.IntegrationAutoConfiguration +org.springframework.integration.config.IntegrationConfigurationInitializer=\ +org.springframework.integration.config.GlobalChannelInterceptorInitializer diff --git a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests-context.xml index 65036f4a7c..af2d32b8f7 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests-context.xml @@ -8,10 +8,16 @@ - - + + + + + + + + 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 ee9c637150..67439cd56c 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 @@ -18,16 +18,24 @@ package org.springframework.integration.configuration; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.concurrent.atomic.AtomicInteger; + import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -43,11 +51,15 @@ import org.springframework.integration.annotation.Payload; import org.springframework.integration.annotation.Publisher; 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.NullChannel; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.channel.interceptor.WireTap; 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.history.MessageHistory; import org.springframework.integration.history.MessageHistoryConfigurer; import org.springframework.integration.support.MessageBuilder; @@ -55,6 +67,9 @@ import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; 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.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -68,24 +83,41 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader; @RunWith(SpringJUnit4ClassRunner.class) public class EnableIntegrationTests { + @Autowired + private ApplicationContext context; + @Autowired private MessageChannel input; @Autowired - private PollableChannel output; + private QueueChannel output; @Autowired private PollableChannel publishedChannel; + @Autowired + private PollableChannel wireTapChannel; + @Autowired private MessageHistoryConfigurer configurer; @Autowired private TestGateway testGateway; + @Autowired + private TestChannelInterceptor testChannelInterceptor; + + @Autowired + private AtomicInteger fbInterceptorCounter; + @Test public void testAnnotatedServiceActivator() { this.input.send(MessageBuilder.withPayload("Foo").build()); + + Message interceptedMessage = this.wireTapChannel.receive(1000); + assertNotNull(interceptedMessage); + assertEquals("Foo", interceptedMessage.getPayload()); + Message receive = this.output.receive(1000); assertNotNull(receive); assertEquals("FOO", receive.getPayload()); @@ -107,19 +139,24 @@ public class EnableIntegrationTests { assertThat(messageHistoryString, Matchers.not(Matchers.containsString("input"))); assertThat(messageHistoryString, Matchers.not(Matchers.containsString("output"))); assertThat(messageHistoryString, Matchers.containsString("publishedChannel")); + + assertNull(this.wireTapChannel.receive(0)); + assertThat(this.testChannelInterceptor.getInvoked(), Matchers.greaterThan(0)); + assertThat(this.fbInterceptorCounter.get(), Matchers.greaterThan(0)); } - @Test @DirtiesContext + @Test + @DirtiesContext public void testChangePatterns() { try { - this.configurer.setComponentNamePatterns(new String[] {"*"}); + this.configurer.setComponentNamePatterns(new String[]{"*"}); fail("ExpectedException"); } catch (IllegalStateException e) { assertThat(e.getMessage(), containsString("cannot be changed")); } this.configurer.stop(); - this.configurer.setComponentNamePatterns(new String[] {"*"}); + this.configurer.setComponentNamePatterns(new String[]{"*"}); assertEquals("*", TestUtils.getPropertyValue(this.configurer, "componentNamePatterns", String[].class)[0]); } @@ -129,6 +166,32 @@ public class EnableIntegrationTests { assertEquals(payload.toUpperCase(), this.testGateway.echo(payload)); } + @Test + public void testParentChildAnnotationConfiguration() { + AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext(); + child.register(ChildConfiguration.class); + child.setParent(this.context); + child.refresh(); + AbstractMessageChannel foo = child.getBean("foo", AbstractMessageChannel.class); + ChannelInterceptor baz = child.getBean("baz", ChannelInterceptor.class); + assertTrue(foo.getChannelInterceptors().contains(baz)); + assertFalse(this.output.getChannelInterceptors().contains(baz)); + child.close(); + } + + @Test + public void testParentChildAnnotationConfigurationFromAnotherPackage() { + AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext(); + child.register(org.springframework.integration.configuration2.ChildConfiguration.class); + child.setParent(this.context); + child.refresh(); + AbstractMessageChannel foo = child.getBean("foo", AbstractMessageChannel.class); + ChannelInterceptor baz = child.getBean("baz", ChannelInterceptor.class); + assertTrue(foo.getChannelInterceptors().contains(baz)); + assertFalse(this.output.getChannelInterceptors().contains(baz)); + child.close(); + } + @Configuration @ComponentScan @IntegrationComponentScan @@ -147,6 +210,65 @@ public class EnableIntegrationTests { return new QueueChannel(); } + @Bean + public PollableChannel wireTapChannel() { + return new QueueChannel(); + } + + + @Bean + @GlobalChannelInterceptor(patterns = "input") + public WireTap wireTap() { + return new WireTap(this.wireTapChannel()); + } + + @Bean + public AtomicInteger fbInterceptorCounter() { + return new AtomicInteger(); + } + + @Bean + @GlobalChannelInterceptor + public FactoryBean ciFactoryBean() { + return new AbstractFactoryBean() { + + @Override + public Class getObjectType() { + return ChannelInterceptor.class; + } + + @Override + protected ChannelInterceptor createInstance() throws Exception { + return new ChannelInterceptorAdapter() { + + @Override + public Message preSend(Message message, MessageChannel channel) { + fbInterceptorCounter().incrementAndGet(); + return super.preSend(message, channel); + } + }; + } + }; + } + + } + + @Component + @GlobalChannelInterceptor + public static class TestChannelInterceptor extends ChannelInterceptorAdapter { + + private final AtomicInteger invoked = new AtomicInteger(); + + @Override + public Message preSend(Message message, MessageChannel channel) { + this.invoked.incrementAndGet(); + return message; + } + + public Integer getInvoked() { + return invoked.get(); + } + } @Configuration @@ -173,6 +295,24 @@ public class EnableIntegrationTests { } + @Configuration + @EnableIntegration + public static class ChildConfiguration { + + @Bean + public MessageChannel foo() { + return new DirectChannel(); + } + + @Bean + @GlobalChannelInterceptor(patterns = "*") + public WireTap baz() { + return new WireTap(new NullChannel()); + } + + } + + @MessageEndpoint public static class AnnotationTestService { @@ -196,7 +336,7 @@ public class EnableIntegrationTests { @MessagingGateway(defaultRequestChannel = "gatewayChannel", defaultHeaders = @GatewayHeader(name = "foo", value = "FOO")) public static interface TestGateway { - @Gateway(headers = @GatewayHeader(name = "calledMethod", expression="#gatewayMethod.name")) + @Gateway(headers = @GatewayHeader(name = "calledMethod", expression = "#gatewayMethod.name")) String echo(String payload); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/configuration2/ChildConfiguration.java b/spring-integration-core/src/test/java/org/springframework/integration/configuration2/ChildConfiguration.java new file mode 100644 index 0000000000..aa2ba4449f --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/configuration2/ChildConfiguration.java @@ -0,0 +1,52 @@ +/* + * 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.configuration2; + +/** + * @author Artem Bilan + * @since 4.0 + */ + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.interceptor.WireTap; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.config.GlobalChannelInterceptor; +import org.springframework.messaging.MessageChannel; + +@Configuration +@EnableIntegration +public class ChildConfiguration { + + @Bean + public MessageChannel foo() { + return new DirectChannel(); + } + + @Bean + public MessageChannel bar() { + return new DirectChannel(); + } + + @Bean + @GlobalChannelInterceptor(patterns = "*") + public WireTap baz() { + return new WireTap(this.bar()); + } + +} diff --git a/src/reference/docbook/overview.xml b/src/reference/docbook/overview.xml index c5ffbbd2d7..08383b64e7 100644 --- a/src/reference/docbook/overview.xml +++ b/src/reference/docbook/overview.xml @@ -359,6 +359,15 @@ for the default channel. See for more information. + + The @GlobalChannelInterceptor annotation has been introduced to + mark ChannelInterceptor beans for global channel interception. + This annotation is an analogue of the <int:channel-interceptor> xml element + (see ). @GlobalChannelInterceptor annotations + can be placed at the class level (with a @Component stereotype annotation), or + on @Bean methods within @Configuration classes. In either case, + the bean must be a ChannelInterceptor. + diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index a68cd54b59..46f2e47032 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -72,6 +72,14 @@ >Spring Boot - AutoConfigure. +

+ @GlobalChannelInterceptor + + As well as the @EnableIntegration annotation mentioned above, + the @GlobalChannelInterceptor annotation has bean introduced. + For more information, see . + +
@EnablePublisher