Skip to content

Commit

Permalink
INT-2434-v3 polishing based on comments and discussions with @markfisher
Browse files Browse the repository at this point in the history
 and @garyrussell. Added support for disabling aut-creatioin of channels
  • Loading branch information
olegz committed Mar 29, 2012
1 parent 7e81e08 commit 6e34aaa
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 41 deletions.
Expand Up @@ -16,6 +16,7 @@

package org.springframework.integration.config.xml;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -91,10 +92,14 @@ protected final AbstractBeanDefinition parseInternal(Element element, ParserCont
String inputChannelName = element.getAttribute(inputChannelAttributeName);

if (!parserContext.getRegistry().containsBeanDefinition(inputChannelName)){
BeanDefinition channelCreatorDefinition = parserContext.getRegistry().getBeanDefinition(AbstractIntegrationNamespaceHandler.CHANNEL_INITIALIZER_BEAN_NAME);
BeanDefinition channelRegistry = parserContext.getRegistry().getBeanDefinition(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME);

@SuppressWarnings("unchecked")
Set<String> channelNames = (Set<String>) channelCreatorDefinition.getConstructorArgumentValues().
getArgumentValue(0, Set.class).getValue();
Set<String> channelNames = (Set<String>) channelRegistry.getAttribute(ChannelInitializer.CHANNEL_NAMES_ATTR);
if (channelNames == null){
channelNames = (Set<String>) new HashSet<String>();

This comment has been minimized.

Copy link
@garyrussell

garyrussell Mar 30, 2012

Shouldn't this be a ManagedSet?

This comment has been minimized.

Copy link
@olegz

olegz Mar 30, 2012

Author Owner

No, since if you remember BeanDefinition attributes are not post-processed. That is why (you'll see from the comments in ChannelInitializer) I first assemble the Set of channel candidate names in the definition of AutoCreateCandidatesCollector, than removing this definition and creating a new definition (which will become a bean) of AutoCreateCandidatesCollector which is initialized with ManagedSet of collected channelNames so all the channelNames could be resolved. Its pretty nifty trick ;)

channelRegistry.setAttribute(ChannelInitializer.CHANNEL_NAMES_ATTR, channelNames);
}
channelNames.add(inputChannelName);
}

Expand Down
Expand Up @@ -16,22 +16,21 @@

package org.springframework.integration.config.xml;

import java.util.Set;

import org.w3c.dom.Element;
import org.w3c.dom.Node;

import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
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.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;

/**
Expand Down Expand Up @@ -68,23 +67,46 @@ public final BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder def
return this.delegate.decorate(source, definition, parserContext);
}

/*
* This method will auto-register ChannelInitializer which could also be overridden by the user
* by simply registering a ChannelInitializer <bean> with 'autoCreate' property set to false.
* It will also register ChannelInitializer$AutoCreateCandidatesCollector which simply collects channelNames to be created.
* It does a little more so please read its comments. ChannelInitializer$AutoCreateCandidatesCollector
* can NOT be overridden by the user

This comment has been minimized.

Copy link
@garyrussell

garyrussell Mar 30, 2012

...register a ChannelInitializer...

...ChannelInitializer with its 'autoCreate' property set to false, to suppress channel creation.

...also register a Channel...

...coments. The Channel...

*/
private void registerImplicitChannelCreator(ParserContext parserContext) {
// ChannelInitializer
boolean alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ListableBeanFactory) {
if (parserContext.getRegistry() instanceof ConfigurableListableBeanFactory) {
// 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);
alreadyRegistered = ((ConfigurableListableBeanFactory) parserContext.getRegistry()).containsBeanDefinition(CHANNEL_INITIALIZER_BEAN_NAME);

This comment has been minimized.

Copy link
@garyrussell

garyrussell Mar 30, 2012

Why change to CLBF?

This comment has been minimized.

Copy link
@olegz

olegz Mar 30, 2012

Author Owner

some leftover. will clean up

}
else {
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(CHANNEL_INITIALIZER_BEAN_NAME);
}
if (!alreadyRegistered) {
BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition(ChannelInitializer.class);
Set<String> channelNames = new ManagedSet<String>();
channelDef.addConstructorArgValue(channelNames);
BeanDefinitionHolder channelCreatorHolder = new BeanDefinitionHolder(channelDef.getBeanDefinition(), CHANNEL_INITIALIZER_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(channelCreatorHolder, parserContext.getRegistry());
}
// ChannelInitializer$AutoCreateCandidatesCollector
alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ConfigurableListableBeanFactory) {
// unlike DefaultConfiguringBeanFactoryPostProcessor we need one of these per registry
// therefore we need to call containsBeanDefinition(..) which does not consider parent registry
alreadyRegistered = ((ConfigurableListableBeanFactory) 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);
BeanDefinitionHolder channelRegistryHolder =
new BeanDefinitionHolder(channelRegistryBuilder.getBeanDefinition(), ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(channelRegistryHolder, parserContext.getRegistry());
}
}

private void registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(ParserContext parserContext) {
Expand Down Expand Up @@ -151,5 +173,4 @@ private void doRegisterBeanDefinitionParser(String elementName, BeanDefinitionPa
}

}

}
Expand Up @@ -15,7 +15,7 @@
*/
package org.springframework.integration.config.xml;

import java.util.Set;
import java.util.Collection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand All @@ -24,47 +24,109 @@
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
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.beans.factory.support.ManagedSet;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.integration.channel.DirectChannel;

/**
* A {@link InitializingBean} implementation that is responsible for creating channels that
* are not explicitly defined but identified via '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}.
*
* A {@link InitializingBean} implementation that is responsible for creating
* channels that are not explicitly defined but identified via 'input-channel'

This comment has been minimized.

Copy link
@garyrussell

garyrussell Mar 30, 2012

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
* @since 2.1.1
*/
class ChannelInitializer implements BeanFactoryAware, InitializingBean {
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 final Set<String> channelNames;
private volatile boolean autoCreate = true;

public ChannelInitializer(Set<String> channelNames){
this.channelNames = channelNames;
public void setAutoCreate(boolean autoCreate) {
this.autoCreate = autoCreate;
}

public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}

public void afterPropertiesSet() throws Exception {
for (String channelName : this.channelNames) {
if (!beanFactory.containsBean(channelName)){
if (this.logger.isDebugEnabled()){
this.logger.debug("Auto-creating channel '" + channelName + "' as DirectChannel");
if (!autoCreate){
return;
}
else {
AutoCreateCandidatesCollector channelCandidatesColector =
(AutoCreateCandidatesCollector) beanFactory.getBean(AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME, AutoCreateCandidatesCollector.class);
// at this point channelNames are all resolved with placeholders and SpEL
Collection<String> channelNames = channelCandidatesColector.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);
}
}
RootBeanDefinition messageChannel = new RootBeanDefinition();
messageChannel.setBeanClass(DirectChannel.class);
BeanDefinitionHolder messageChannelHolder = new BeanDefinitionHolder(messageChannel, channelName);
BeanDefinitionReaderUtils.registerBeanDefinition(messageChannelHolder, (BeanDefinitionRegistry) this.beanFactory);
}
}
}

/*
* This class serves two purposes
* 1. A BeanDefinition that collects candidate channel names as its BeanDefinition attributes
* 2. A holder of the resolved (property placeholders and SpEL expressions) channel names to be auto-created by the
* ChannelInitializer.
* Once this bean reaches Factory post processing phase it creates a new definition of itself injecting it with
* the ManagedSet of channelNames property. The ManagedSet will be naturally resolved before it is used by the
* ChannelInitializer. It also unregisters its old definition (since its only value was to provide a place
* to collect candidate channel names) and registers new definition of itself with "to-be resolved" channelNames
* property
*/
public static class AutoCreateCandidatesCollector implements BeanFactoryPostProcessor{

This comment has been minimized.

Copy link
@garyrussell

garyrussell Mar 30, 2012

It's not clear to me why this is necessary - can't we simply have a ManagedMap<> of channel names as a property on the C.I. ??

This comment has been minimized.

Copy link
@olegz

olegz via email Mar 30, 2012

Author Owner

This comment has been minimized.

Copy link
@garyrussell

garyrussell Mar 30, 2012

Yes, after I left my note, I realized the problem is the bean definition can be overridden and you'd lose the channel names. So, yes, I see why a separate bean is needed for that but, as you say, it doesn't need the delete and recreate dance.


private volatile Collection<String> channelNames;

public void setChannelNames(Collection<String> channelNames) {
this.channelNames = channelNames;
}

public Collection<String> getChannelNames() {
return channelNames;
}

@SuppressWarnings("unchecked")
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition definition = beanFactory.getBeanDefinition(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME);
Collection<String> _channelNames = (Collection<String>) definition.getAttribute(ChannelInitializer.CHANNEL_NAMES_ATTR);
if (_channelNames != null){
((BeanDefinitionRegistry)beanFactory).removeBeanDefinition(ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME);
BeanDefinitionBuilder candidatesBuilder = BeanDefinitionBuilder.genericBeanDefinition(this.getClass().getName());
ManagedSet<String> candidateChannelNamesToBeResolved = new ManagedSet<String>();
candidateChannelNamesToBeResolved.addAll(_channelNames);
candidatesBuilder.addPropertyValue("channelNames", candidateChannelNamesToBeResolved);
BeanDefinitionHolder holder = new BeanDefinitionHolder(candidatesBuilder.getBeanDefinition(),
ChannelInitializer.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, (BeanDefinitionRegistry) beanFactory);
}
}
}
Expand Down
Expand Up @@ -36,7 +36,6 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
Expand Down
Expand Up @@ -15,11 +15,9 @@
<prop key="channelC" >channelC</prop>
<prop key="channelB" >channelB</prop>
</util:properties>



<int:service-activator id="serviceA" input-channel="channelA" output-channel="#{'channelB'}" expression="''"/>

<int:service-activator id="serviceB" input-channel="#{'channelB'}" output-channel="${channelC}" expression="''"/>

<int:service-activator id="serviceC" input-channel="${channelC}" output-channel="#{@channelD.getComponentName()}" expression="''"/>
Expand Down
Expand Up @@ -24,10 +24,25 @@
* @author Oleg Zhurakousky
*
*/
public class TestDisableChannelAutoCreationTests {
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 testDisablingAutoChannelCreation(){
new ClassPathXmlApplicationContext("TestDisableChannelAutoCreation-context.xml", this.getClass());
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());
}
}
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">

<int:service-activator input-channel="inputChannel" expression="'hello'"/>

<bean id="ChannelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<property name="autoCreate" value="false"/>
</bean>

</beans>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">

<bean id="ChannelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<property name="autoCreate" value="false"/>
</bean>

<int:service-activator input-channel="inputChannel" expression="'hello'"/>

</beans>
Expand Up @@ -5,9 +5,10 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">


<int:service-activator input-channel="inputChannel" expression="'hello'"/>

<bean id="ChannelInitializer" class="java.lang.Object"/>
<bean id="ChannelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<property name="autoCreate" value="true"/>
</bean>

</beans>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd">

<bean id="ChannelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<property name="autoCreate" value="true"/>
</bean>

<int:service-activator input-channel="inputChannel" expression="'hello'"/>

</beans>

0 comments on commit 6e34aaa

Please sign in to comment.