From 458eac43da83c1b8c55070a17721e13c1be4cb47 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 9 Feb 2012 17:56:27 -0500 Subject: [PATCH] INT-2434 Fix How input-channels are auto-created Channel creation logic for channels that are not explicitly defined but identified via an 'input-channel' attribute on the corresponding endpoint is now done by ChannelInitializer - an InitializingBean implementation. This bean plays a role of pre-instantiator since it is instantiated and initialized as the very first bean of all SI beans using AbstractIntegrationNamespaceHandler INT-2434-v3 polishing INT-2434-v3 polishing, changed ChannelCreatingFactoryBean from BeanFactoryPostProcessor to BeanFactoryAware INT-2434 polishing fix the test changed the default name of the CHANNEL_CREATOR_BEAN_NAME to a simple name added additional test changed ChannelCreatingFactoryBean from FactoryBean to InitializingBean and renamed it to ChannelInitializer INT-2434 added test validating how automatic channel creation can be disabled INT-2434 polished based on the latest PR comments INT-2434-v3 polishing based on comments and discussions with @markfisher and @garyrussell. Added support for disabling aut-creatioin of channels INT-2434-v3 polished based on @garyrussell last comments INT-2434-v3 final polishing --- spring-integration-core/.springBeans | 17 --- .../xml/AbstractConsumerEndpointParser.java | 58 +++++----- .../AbstractIntegrationNamespaceHandler.java | 49 +++++++- .../config/xml/ChannelInitializer.java | 108 ++++++++++++++++++ .../channel/DirectChannelTests.java | 64 ++++++++++- .../channel/channel-override-config.xml | 37 ++++++ .../integration/channel/parent-config.xml | 12 ++ .../config/xml/ChannelAutoCreationTests.java | 48 ++++++++ ...sableChannelAutoCreation-after-context.xml | 14 +++ ...ableChannelAutoCreation-before-context.xml | 14 +++ ...nableChannelAutoCreation-after-context.xml | 14 +++ ...ableChannelAutoCreation-before-context.xml | 14 +++ .../jmx/config/MBeanExporterHelper.java | 34 +++++- .../mbeanexporterhelper/Int2307Tests.java | 13 ++- .../single-config-custom-exporter.xml | 10 +- .../mbeanexporterhelper/single-config.xml | 16 ++- 16 files changed, 456 insertions(+), 66 deletions(-) delete mode 100644 spring-integration-core/.springBeans create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChannelInitializer.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/channel/channel-override-config.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/channel/parent-config.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/config/xml/ChannelAutoCreationTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestDisableChannelAutoCreation-after-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestDisableChannelAutoCreation-before-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-after-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-before-context.xml diff --git a/spring-integration-core/.springBeans b/spring-integration-core/.springBeans deleted file mode 100644 index 6a45d2853eb..00000000000 --- a/spring-integration-core/.springBeans +++ /dev/null @@ -1,17 +0,0 @@ - - - 1 - - - - - - - src/test/java/org/springframework/integration/gateway/GatewayInterfaceTest-context.xml - src/test/java/org/springframework/integration/gateway/InnerGatewayWithChainTests-context.xml - src/test/java/org/springframework/integration/history/annotated-config.xml - src/test/java/org/springframework/integration/aggregator/config.xml - - - - diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java index 71498c7464a..769477ca89f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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,19 +16,22 @@ package org.springframework.integration.config.xml; +import java.util.Collection; import java.util.List; import org.w3c.dom.Element; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; 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.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.context.IntegrationContextUtils; +import org.springframework.integration.config.ConsumerEndpointFactoryBean; import org.springframework.util.CollectionUtils; import org.springframework.util.xml.DomUtils; @@ -46,7 +49,6 @@ public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinit protected static final String EXPRESSION_ATTRIBUTE = "expression"; - @Override protected boolean shouldGenerateId() { return false; @@ -83,28 +85,34 @@ protected final AbstractBeanDefinition parseInternal(Element element, ParserCont return handlerBeanDefinition; } - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".config.ConsumerEndpointFactoryBean"); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class); String handlerBeanName = BeanDefinitionReaderUtils.generateBeanName(handlerBeanDefinition, parserContext.getRegistry()); parserContext.registerBeanComponent(new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName)); - + builder.addPropertyReference("handler", handlerBeanName); String inputChannelName = element.getAttribute(inputChannelAttributeName); - boolean channelExists = false; - if (parserContext.getRegistry() instanceof BeanFactory) { - // BeanFactory also checks ancestor contexts in a hierarchy - channelExists = ((BeanFactory) parserContext.getRegistry()).containsBean(inputChannelName); - } - else { - channelExists = parserContext.getRegistry().containsBeanDefinition(inputChannelName); - } - if (!channelExists && this.shouldAutoCreateChannel(inputChannelName)) { - BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.DirectChannel"); - BeanDefinitionHolder holder = new BeanDefinitionHolder(channelDef.getBeanDefinition(), inputChannelName); - BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry()); + + if (!parserContext.getRegistry().containsBeanDefinition(inputChannelName)){ + if (parserContext.getRegistry().containsBeanDefinition(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME)){ + BeanDefinition channelRegistry = parserContext.getRegistry(). + getBeanDefinition(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME); + ConstructorArgumentValues caValues = channelRegistry.getConstructorArgumentValues(); + ValueHolder vh = caValues.getArgumentValue(0, Collection.class); + if (vh == null){ //although it should never happen if it does we can fix it + caValues.addIndexedArgumentValue(0, new ManagedSet()); + } + + @SuppressWarnings("unchecked") + Collection channelCandidateNames = (Collection) caValues.getArgumentValue(0, Collection.class).getValue(); + channelCandidateNames.add(inputChannelName); + } + else { + parserContext.getReaderContext().error("Failed to locate '" + + ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME + "'", parserContext.getRegistry()); + } } + builder.addPropertyValue("inputChannelName", inputChannelName); List pollerElementList = DomUtils.getChildElementsByTagName(element, "poller"); if (!CollectionUtils.isEmpty(pollerElementList)) { @@ -120,10 +128,4 @@ protected final AbstractBeanDefinition parseInternal(Element element, ParserCont parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName)); return null; } - - private boolean shouldAutoCreateChannel(String channelName) { - return !IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME.equals(channelName) - && !IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME.equals(channelName); - } - -} +} \ No newline at end of file diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java index 23be8df71ae..a1b6e98de5d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -24,11 +24,13 @@ import org.springframework.beans.factory.config.BeanDefinitionHolder; 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.xml.BeanDefinitionDecorator; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.NamespaceHandler; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.ChannelInitializer.AutoCreateCandidatesCollector; import org.springframework.util.StringUtils; /** @@ -36,11 +38,14 @@ * for configuring default bean definitions. * * @author Mark Fisher + * @author Oleg Zhurakousky */ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHandler { private static final String VERSION = "2.1"; + public static final String CHANNEL_INITIALIZER_BEAN_NAME = ChannelInitializer.class.getSimpleName(); + private static final String DEFAULT_CONFIGURING_POSTPROCESSOR_SIMPLE_CLASS_NAME = "DefaultConfiguringBeanFactoryPostProcessor"; @@ -53,6 +58,7 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa public final BeanDefinition parse(Element element, ParserContext parserContext) { this.verifySchemaVersion(element, parserContext); + this.registerImplicitChannelCreator(parserContext); this.registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(parserContext); return this.delegate.parse(element, parserContext); } @@ -61,6 +67,46 @@ public final BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder def return this.delegate.decorate(source, definition, parserContext); } + /* + * This method will auto-register a ChannelInitializer which could also be overridden by the user + * by simply registering a ChannelInitializer with its 'autoCreate' property set to false to suppress channel creation. + * It will also register a ChannelInitializer$AutoCreateCandidatesCollector which simply collects candidate channel names. + */ + private void registerImplicitChannelCreator(ParserContext parserContext) { + // ChannelInitializer + boolean alreadyRegistered = false; + if (parserContext.getRegistry() instanceof ListableBeanFactory) { + // unlike DefaultConfiguringBeanFactoryPostProcessor we need one of these per registry + // therefore we need to call containsBeanDefinition(..) which does not consider parent registry + alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBeanDefinition(CHANNEL_INITIALIZER_BEAN_NAME); + } + else { + alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(CHANNEL_INITIALIZER_BEAN_NAME); + } + if (!alreadyRegistered) { + BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition(ChannelInitializer.class); + BeanDefinitionHolder channelCreatorHolder = new BeanDefinitionHolder(channelDef.getBeanDefinition(), CHANNEL_INITIALIZER_BEAN_NAME); + BeanDefinitionReaderUtils.registerBeanDefinition(channelCreatorHolder, parserContext.getRegistry()); + } + // ChannelInitializer$AutoCreateCandidatesCollector + if (parserContext.getRegistry() instanceof ListableBeanFactory) { + // unlike DefaultConfiguringBeanFactoryPostProcessor we need one of these per registry + // therefore we need to call containsBeanDefinition(..) which does not consider parent registry + alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()). + containsBeanDefinition(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME); + } + else { + alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME); + } + if (!alreadyRegistered) { + BeanDefinitionBuilder channelRegistryBuilder = BeanDefinitionBuilder.genericBeanDefinition(AutoCreateCandidatesCollector.class); + channelRegistryBuilder.addConstructorArgValue(new ManagedSet()); + BeanDefinitionHolder channelRegistryHolder = + new BeanDefinitionHolder(channelRegistryBuilder.getBeanDefinition(), ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME); + BeanDefinitionReaderUtils.registerBeanDefinition(channelRegistryHolder, parserContext.getRegistry()); + } + } + private void registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(ParserContext parserContext) { boolean alreadyRegistered = false; if (parserContext.getRegistry() instanceof ListableBeanFactory) { @@ -125,5 +171,4 @@ private void doRegisterBeanDefinitionParser(String elementName, BeanDefinitionPa } } - } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChannelInitializer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChannelInitializer.java new file mode 100644 index 00000000000..eab471262a7 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ChannelInitializer.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2012 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.xml; + +import java.util.Collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.util.Assert; + +/** + * A {@link InitializingBean} implementation that is responsible for creating + * channels that are not explicitly defined but identified via the 'input-channel' + * attribute of the corresponding endpoints. + * + * This bean plays a role of pre-instantiator since it is instantiated and + * initialized as the very first bean of all SI beans using + * {@link AbstractIntegrationNamespaceHandler}. + * + * @author Oleg Zhurakousky + * @since 2.1.1 + */ +final class ChannelInitializer implements BeanFactoryAware, InitializingBean { + + public static String AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME = "$autoCreateChannelCandidates"; + public static String CHANNEL_NAMES_ATTR = "channelNames"; + + private Log logger = LogFactory.getLog(this.getClass()); + + private volatile BeanFactory beanFactory; + + private volatile boolean autoCreate = true; + + public void setAutoCreate(boolean autoCreate) { + this.autoCreate = autoCreate; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(this.beanFactory, "'beanFactory' must not be null"); + if (!autoCreate){ + return; + } + else { + AutoCreateCandidatesCollector channelCandidatesCollector = + (AutoCreateCandidatesCollector) beanFactory.getBean(AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME, AutoCreateCandidatesCollector.class); + Assert.notNull(channelCandidatesCollector, "Failed to locate '" + + ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME); + // at this point channelNames are all resolved with placeholders and SpEL + Collection channelNames = channelCandidatesCollector.getChannelNames(); + if (channelNames != null){ + for (String channelName : channelNames) { + if (!beanFactory.containsBean(channelName)){ + if (this.logger.isDebugEnabled()){ + this.logger.debug("Auto-creating channel '" + channelName + "' as DirectChannel"); + } + RootBeanDefinition messageChannel = new RootBeanDefinition(); + messageChannel.setBeanClass(DirectChannel.class); + BeanDefinitionHolder messageChannelHolder = new BeanDefinitionHolder(messageChannel, channelName); + BeanDefinitionReaderUtils.registerBeanDefinition(messageChannelHolder, (BeanDefinitionRegistry) this.beanFactory); + } + } + } + } + } + + /* + * Collects candidate channel names to be auto-created by ChannelInitializer + */ + static class AutoCreateCandidatesCollector { + + private final Collection channelNames; + + public AutoCreateCandidatesCollector(Collection channelNames){ + this.channelNames = channelNames; + } + + public Collection getChannelNames() { + return channelNames; + } + } +} \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/DirectChannelTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/DirectChannelTests.java index 65f46609a41..3455abee1de 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/channel/DirectChannelTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/DirectChannelTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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,20 +16,27 @@ package org.springframework.integration.channel; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - +import java.lang.reflect.Method; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy; import org.springframework.integration.dispatcher.UnicastingDispatcher; +import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * @author Mark Fisher @@ -68,6 +75,55 @@ public void run() { assertEquals("test-thread", target.threadName); } + @Test // See INT-2434 + public void testChannelCreationWithBeanDefinitionOverrideTrue() throws Exception { + ClassPathXmlApplicationContext parentContext = new ClassPathXmlApplicationContext("parent-config.xml", this.getClass()); + MessageChannel parentChannelA = parentContext.getBean("parentChannelA", MessageChannel.class); + MessageChannel parentChannelB = parentContext.getBean("parentChannelB", MessageChannel.class); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(); + context.setAllowBeanDefinitionOverriding(false); + context.setConfigLocations(new String[]{"classpath:org/springframework/integration/channel/channel-override-config.xml"}); + context.setParent(parentContext); + Method method = ReflectionUtils.findMethod(ClassPathXmlApplicationContext.class, "obtainFreshBeanFactory"); + method.setAccessible(true); + method.invoke(context); + assertFalse(context.containsBean("channelA")); + assertFalse(context.containsBean("channelB")); + assertTrue(context.containsBean("channelC")); + assertTrue(context.containsBean("channelD")); + + context.refresh(); + + PublishSubscribeChannel channelEarly = context.getBean("channelEarly", PublishSubscribeChannel.class); + + assertTrue(context.containsBean("channelA")); + assertTrue(context.containsBean("channelB")); + assertTrue(context.containsBean("channelC")); + assertTrue(context.containsBean("channelD")); + EventDrivenConsumer consumerA = context.getBean("serviceA", EventDrivenConsumer.class); + assertEquals(context.getBean("channelA"), TestUtils.getPropertyValue(consumerA, "inputChannel")); + assertEquals(context.getBean("channelB"), TestUtils.getPropertyValue(consumerA, "handler.outputChannel")); + + EventDrivenConsumer consumerB = context.getBean("serviceB", EventDrivenConsumer.class); + assertEquals(context.getBean("channelB"), TestUtils.getPropertyValue(consumerB, "inputChannel")); + assertEquals(context.getBean("channelC"), TestUtils.getPropertyValue(consumerB, "handler.outputChannel")); + + EventDrivenConsumer consumerC = context.getBean("serviceC", EventDrivenConsumer.class); + assertEquals(context.getBean("channelC"), TestUtils.getPropertyValue(consumerC, "inputChannel")); + assertEquals(context.getBean("channelD"), TestUtils.getPropertyValue(consumerC, "handler.outputChannel")); + + EventDrivenConsumer consumerD = context.getBean("serviceD", EventDrivenConsumer.class); + assertEquals(parentChannelA, TestUtils.getPropertyValue(consumerD, "inputChannel")); + assertEquals(parentChannelB, TestUtils.getPropertyValue(consumerD, "handler.outputChannel")); + + EventDrivenConsumer consumerE = context.getBean("serviceE", EventDrivenConsumer.class); + assertEquals(parentChannelB, TestUtils.getPropertyValue(consumerE, "inputChannel")); + + EventDrivenConsumer consumerF = context.getBean("serviceF", EventDrivenConsumer.class); + assertEquals(channelEarly, TestUtils.getPropertyValue(consumerF, "inputChannel")); + } + private static class ThreadNameExtractingTestTarget implements MessageHandler { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/channel-override-config.xml b/spring-integration-core/src/test/java/org/springframework/integration/channel/channel-override-config.xml new file mode 100644 index 00000000000..c2bc3d4d9f2 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/channel-override-config.xml @@ -0,0 +1,37 @@ + + + + + + + channelC + channelB + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/parent-config.xml b/spring-integration-core/src/test/java/org/springframework/integration/channel/parent-config.xml new file mode 100644 index 00000000000..dd3b9a22985 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/parent-config.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ChannelAutoCreationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ChannelAutoCreationTests.java new file mode 100644 index 00000000000..7f651199d23 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ChannelAutoCreationTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2012 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.xml; + +import org.junit.Test; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.context.support.ClassPathXmlApplicationContext; +/** + * + * @author Oleg Zhurakousky + * + */ +public class ChannelAutoCreationTests { + + @Test // no assertions since it validates that no exception is thrown + public void testEnablingAutoChannelCreationBeforeWithCustom(){ + new ClassPathXmlApplicationContext("TestEnableChannelAutoCreation-before-context.xml", this.getClass()); + } + + @Test // no assertions since it validates that no exception is thrown + public void testEnablingAutoChannelCreationAfterWithCustom(){ + new ClassPathXmlApplicationContext("TestEnableChannelAutoCreation-after-context.xml", this.getClass()); + } + + @Test(expected=BeanCreationException.class) + public void testDisablingAutoChannelCreationAfter(){ + new ClassPathXmlApplicationContext("TestDisableChannelAutoCreation-after-context.xml", this.getClass()); + } + + @Test(expected=BeanCreationException.class) + public void testDisablingAutoChannelCreationBefore(){ + new ClassPathXmlApplicationContext("TestDisableChannelAutoCreation-before-context.xml", this.getClass()); + } +} \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestDisableChannelAutoCreation-after-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestDisableChannelAutoCreation-after-context.xml new file mode 100644 index 00000000000..47524a43344 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestDisableChannelAutoCreation-after-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestDisableChannelAutoCreation-before-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestDisableChannelAutoCreation-before-context.xml new file mode 100644 index 00000000000..18d97ef9ad6 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestDisableChannelAutoCreation-before-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-after-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-after-context.xml new file mode 100644 index 00000000000..feb3b0fb5cd --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-after-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-before-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-before-context.xml new file mode 100644 index 00000000000..01e60a68fc0 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-before-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterHelper.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterHelper.java index 3ad0adb7b3d..f25b838558c 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterHelper.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterHelper.java @@ -12,16 +12,19 @@ */ package org.springframework.integration.jmx.config; +import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.core.PriorityOrdered; +import org.springframework.core.Ordered; import org.springframework.integration.monitor.IntegrationMBeanExporter; import org.springframework.jmx.export.MBeanExporter; import org.springframework.util.StringUtils; @@ -31,24 +34,43 @@ * It helps in eliminating conflicts when more than one MBeanExporter is present. It creates a list * of bean names that will be exported by the IntegrationMBeanExporter and merges it with the list * of 'excludedBeans' of MBeanExporter so it will not attempt to export them again. - * + * * @author Oleg Zhurakousky * @since 2.1 * */ -class MBeanExporterHelper implements BeanFactoryPostProcessor, BeanPostProcessor, PriorityOrdered { +class MBeanExporterHelper implements BeanFactoryPostProcessor, + BeanPostProcessor, Ordered, BeanFactoryAware { private final static String EXCLUDED_BEANS_PROPERTY_NAME = "excludedBeans"; private final static String SI_ROOT_PACKAGE = "org.springframework.integration."; private final Set siBeanNames = new HashSet(); + + private volatile BeanFactory beanFactory; + + private volatile boolean capturedAutoChannelCandidates; - @SuppressWarnings("unchecked") + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof MBeanExporter && !(bean instanceof IntegrationMBeanExporter)){ + if (!this.capturedAutoChannelCandidates && this.beanFactory != null) { + Object autoCreateChannelCandidates = beanFactory.getBean("$autoCreateChannelCandidates"); + if (autoCreateChannelCandidates != null){ + @SuppressWarnings("unchecked") + Collection autoCreateChannelCandidatesNames = + (Collection) new DirectFieldAccessor(autoCreateChannelCandidates).getPropertyValue("channelNames"); + this.siBeanNames.addAll(autoCreateChannelCandidatesNames); + } + this.capturedAutoChannelCandidates = true; + } + if (bean instanceof MBeanExporter && !(bean instanceof IntegrationMBeanExporter)) { MBeanExporter mbeanExporter = (MBeanExporter) bean; DirectFieldAccessor mbeDfa = new DirectFieldAccessor(mbeanExporter); + @SuppressWarnings("unchecked") Set excludedNames = (Set) mbeDfa.getPropertyValue(EXCLUDED_BEANS_PROPERTY_NAME); if (excludedNames != null) { siBeanNames.addAll(excludedNames); @@ -79,6 +101,6 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) } public int getOrder() { - return Integer.MIN_VALUE; + return Ordered.HIGHEST_PRECEDENCE; } } diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java index de1a6e5b808..9a4c7dc4643 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/Int2307Tests.java @@ -34,9 +34,10 @@ */ public class Int2307Tests { + @SuppressWarnings("unchecked") @Test public void testInt2307_DefaultMBeanExporter() throws Exception{ - new ClassPathXmlApplicationContext("single-config.xml", this.getClass()); + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("single-config.xml", this.getClass()); List servers = MBeanServerFactory.findMBeanServer(null); assertEquals(1, servers.size()); MBeanServer server = servers.get(0); @@ -61,6 +62,11 @@ public void testInt2307_DefaultMBeanExporter() throws Exception{ assertEquals(0xf, bits); assertEquals(4, count); + Class clazz = Class.forName("org.springframework.integration.jmx.config.MBeanExporterHelper"); + Object mBeanExporterHelper = context.getBean(clazz); + assertTrue(((Set)TestUtils.getPropertyValue(mBeanExporterHelper, "siBeanNames")).contains("z")); + assertTrue(((Set)TestUtils.getPropertyValue(mBeanExporterHelper, "siBeanNames")).contains("zz")); + // make sure there are no duplicate MBean ObjectNames if 2 contexts loaded from same config new ClassPathXmlApplicationContext("single-config.xml", this.getClass()); } @@ -76,7 +82,10 @@ public void testInt2307_CustomMBeanExporter() throws Exception{ assertTrue(excludedBeanNames.contains("x")); assertTrue(excludedBeanNames.contains("y")); assertTrue(excludedBeanNames.contains("foo")); // non SI bean + Class clazz = Class.forName("org.springframework.integration.jmx.config.MBeanExporterHelper"); + Object mBeanExporterHelper = context.getBean(clazz); + assertTrue(((Set)TestUtils.getPropertyValue(mBeanExporterHelper, "siBeanNames")).contains("z")); } - + public static class Foo{} } diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml index a3b926d6b33..0452a971bf2 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config-custom-exporter.xml @@ -24,18 +24,20 @@ - + - + - + - + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml index 4dd3005e3aa..a8f69d5d33d 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration_/mbeanexporterhelper/single-config.xml @@ -22,16 +22,26 @@ - + - + - + + + + + + + zz + + + +