diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorBeanPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorProcessor.java similarity index 53% rename from spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorBeanPostProcessor.java rename to spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorProcessor.java index 1e972ccbf2..efbdaaddeb 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorBeanPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorProcessor.java @@ -17,20 +17,27 @@ package org.springframework.integration.channel.interceptor; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +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.messaging.MessageChannel; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; @@ -40,59 +47,85 @@ import org.springframework.util.StringUtils; * @author Oleg Zhurakousky * @author Mark Fisher * @author Artem Bilan + * @author Gary Russell * @since 2.0 */ -final class GlobalChannelInterceptorBeanPostProcessor implements BeanPostProcessor, InitializingBean { +final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, SmartLifecycle { - private static final Log logger = LogFactory.getLog(GlobalChannelInterceptorBeanPostProcessor.class); + private static final Log logger = LogFactory.getLog(GlobalChannelInterceptorProcessor.class); private final OrderComparator comparator = new OrderComparator(); - private volatile List channelInterceptors; - private final Set positiveOrderInterceptors = new LinkedHashSet(); private final Set negativeOrderInterceptors = new LinkedHashSet(); + private ListableBeanFactory beanFactory; - GlobalChannelInterceptorBeanPostProcessor(List channelInterceptors) { - this.channelInterceptors = channelInterceptors; - } - + private volatile boolean processed; @Override - public void afterPropertiesSet() throws Exception { - for (GlobalChannelInterceptorWrapper channelInterceptor : this.channelInterceptors) { - if (channelInterceptor.getOrder() >= 0) { - this.positiveOrderInterceptors.add(channelInterceptor); + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ListableBeanFactory.class, beanFactory); + this.beanFactory = (ListableBeanFactory) beanFactory; + } + + @Override + public synchronized void start() { + if (!this.processed) { + Collection interceptors = this.beanFactory.getBeansOfType(GlobalChannelInterceptorWrapper.class).values(); + if (CollectionUtils.isEmpty(interceptors)) { + logger.debug("No global channel interceptors."); } else { - this.negativeOrderInterceptors.add(channelInterceptor); + for (GlobalChannelInterceptorWrapper channelInterceptor : interceptors) { + if (channelInterceptor.getOrder() >= 0) { + this.positiveOrderInterceptors.add(channelInterceptor); + } + else { + this.negativeOrderInterceptors.add(channelInterceptor); + } + } + Map channels = this.beanFactory.getBeansOfType(ChannelInterceptorAware.class); + for (Entry entry : channels.entrySet()) { + this.addMatchingInterceptors(entry.getValue(), entry.getKey()); + } } + this.processed = true; } } @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; + public void stop() { } @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof ChannelInterceptorAware && bean instanceof MessageChannel) { - if (logger.isDebugEnabled()) { - logger.debug("Applying global interceptors on channel '" + beanName + "'"); - } - this.addMatchingInterceptors((ChannelInterceptorAware) bean, beanName); - } - return bean; + public boolean isRunning() { + return false; + } + + @Override + public int getPhase() { + return Integer.MIN_VALUE; + } + + @Override + public boolean isAutoStartup() { + return true; + } + + @Override + public void stop(Runnable callback) { } /** * Adds any interceptor whose pattern matches against the channel's name. */ private void addMatchingInterceptors(ChannelInterceptorAware channel, String beanName) { + if (logger.isDebugEnabled()) { + logger.debug("Applying global interceptors on channel '" + beanName + "'"); + } List tempInterceptors = new ArrayList(); for (GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper : this.positiveOrderInterceptors) { String[] patterns = globalChannelInterceptorWrapper.getPatterns(); @@ -103,7 +136,11 @@ final class GlobalChannelInterceptorBeanPostProcessor implements BeanPostProcess } Collections.sort(tempInterceptors, this.comparator); for (GlobalChannelInterceptorWrapper next : tempInterceptors) { - channel.addInterceptor(next.getChannelInterceptor()); + ChannelInterceptor channelInterceptor = next.getChannelInterceptor(); + if (!(channelInterceptor instanceof VetoCapableInterceptor) + || ((VetoCapableInterceptor) channelInterceptor).shouldIntercept(beanName, channel)) { + channel.addInterceptor(channelInterceptor); + } } tempInterceptors.clear(); @@ -117,7 +154,11 @@ final class GlobalChannelInterceptorBeanPostProcessor implements BeanPostProcess Collections.sort(tempInterceptors, comparator); if (!tempInterceptors.isEmpty()) { for (int i = tempInterceptors.size() - 1; i >= 0; i--) { - channel.addInterceptor(0, tempInterceptors.get(i).getChannelInterceptor()); + ChannelInterceptor channelInterceptor = tempInterceptors.get(i).getChannelInterceptor(); + if (!(channelInterceptor instanceof VetoCapableInterceptor) + || ((VetoCapableInterceptor) channelInterceptor).shouldIntercept(beanName, channel)) { + channel.addInterceptor(0, channelInterceptor); + } } } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/VetoCapableInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/VetoCapableInterceptor.java new file mode 100644 index 0000000000..a6536b7a1e --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/VetoCapableInterceptor.java @@ -0,0 +1,39 @@ +/* + * 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.channel.interceptor; + +import org.springframework.integration.channel.ChannelInterceptorAware; +import org.springframework.messaging.support.ChannelInterceptor; + +/** + * {@link ChannelInterceptor}s implementing this interface can veto + * global interception of a particular channel name. Could be used, for example, + * when an interceptor itself writes to an output channel (which should + * not be intercepted with this interceptor). + * + * @author Gary Russell + * @since 4.0 + * + */ +public interface VetoCapableInterceptor { + + /** + * @param beanName The channel name. + * @return false if the intercept wishes to veto the interception. + */ + boolean shouldIntercept(String beanName, ChannelInterceptorAware channel); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/WireTap.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/WireTap.java index 4f5b6dd899..6d8840bac4 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/WireTap.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/interceptor/WireTap.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. @@ -20,6 +20,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.Lifecycle; +import org.springframework.integration.channel.ChannelInterceptorAware; import org.springframework.integration.core.MessageSelector; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; @@ -35,9 +36,10 @@ import org.springframework.util.Assert; * to a secondary target while still sending the original message to the main channel. * * @author Mark Fisher + * @author Gary Russell */ @ManagedResource -public class WireTap extends ChannelInterceptorAdapter implements Lifecycle { +public class WireTap extends ChannelInterceptorAdapter implements Lifecycle, VetoCapableInterceptor { private static final Log logger = LogFactory.getLog(WireTap.class); @@ -133,4 +135,9 @@ public class WireTap extends ChannelInterceptorAdapter implements Lifecycle { return message; } + @Override + public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) { + return !this.channel.equals(channel); + } + } 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 36a0db027f..6ac44b532f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 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. @@ -31,6 +31,7 @@ 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; /** @@ -39,6 +40,7 @@ import org.springframework.util.xml.DomUtils; * @author Oleg Zhurakousky * @author Mark Fisher * @author David Turanski + * @author Gary Russell * @since 2.0 */ public class GlobalChannelInterceptorParser extends AbstractBeanDefinitionParser { @@ -49,14 +51,13 @@ public class GlobalChannelInterceptorParser extends AbstractBeanDefinitionParser private static final String REF_ATTRIBUTE = "ref"; - private static final String GLOBAL_POST_PROCESSOR_CLASSNAME = "GlobalChannelInterceptorBeanPostProcessor"; + private static final String GLOBAL_INTERCEPTOR_PROCESSOR_CLASSNAME = "GlobalChannelInterceptorProcessor"; private final ManagedList globalInterceptors = new ManagedList(); - private volatile boolean postProcessorCreated; - + @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { this.createAndRegisterGlobalPostProcessorIfNecessary(parserContext); BeanDefinitionBuilder globalChannelInterceptorBuilder = BeanDefinitionBuilder.genericBeanDefinition(GlobalChannelInterceptorWrapper.class); @@ -73,14 +74,12 @@ public class GlobalChannelInterceptorParser extends AbstractBeanDefinitionParser } private void createAndRegisterGlobalPostProcessorIfNecessary(ParserContext parserContext) { - if (!this.postProcessorCreated) { - BeanDefinitionBuilder postProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( - BASE_PACKAGE + GLOBAL_POST_PROCESSOR_CLASSNAME); - postProcessorBuilder.addConstructorArgValue(this.globalInterceptors); - BeanDefinition beanDef = postProcessorBuilder.getBeanDefinition(); - String beanName = BeanDefinitionReaderUtils.generateBeanName(beanDef, parserContext.getRegistry()); - parserContext.registerBeanComponent(new BeanComponentDefinition(beanDef, beanName)); - this.postProcessorCreated = true; + 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)); } } 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 e207f68c78..2e02516f5a 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 @@ -79,7 +79,11 @@ public abstract class IntegrationContextUtils { public static final String INTEGRATION_DATATYPE_CHANNEL_MESSAGE_CONVERTER_BEAN_NAME = "datatypeChannelMessageConverter"; public static final String INTEGRATION_FIXED_SUBSCRIBER_CHANNEL_BPP_BEAN_NAME = "fixedSubscriberChannelBeanFactoryPostProcessor"; + public static final String INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME = "messageBuilderFactory"; + + public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "gloabelChannelInterceptorProcessor"; + /** * @param beanFactory BeanFactory for lookup, must not be null. * @return The {@link MetadataStore} bean whose name is "metadataStore". diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalWireTapTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalWireTapTests.java index 9d2f8bf3d4..fe4e2ce7e2 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalWireTapTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalWireTapTests.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2011 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. 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. @@ -18,6 +18,7 @@ import static org.junit.Assert.assertNull; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.messaging.Message; @@ -27,73 +28,74 @@ import org.springframework.messaging.support.GenericMessage; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** - * + * * @author David Turanski + * @author Gary Russell * */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) public class GlobalWireTapTests { - - + + @Autowired @Qualifier("channel") MessageChannel channel; - + @Autowired @Qualifier("random-channel") MessageChannel anotherChannel; - - - + + + @Autowired @Qualifier("wiretap-single") PollableChannel wiretapSingle; - + @Autowired @Qualifier("wiretap-all") PollableChannel wiretapAll; - - - + + + @Test public void testWireTapsOnTargetedChannel(){ - Message message = new GenericMessage("hello"); + Message message = new GenericMessage("hello"); channel.send(message); Message wireTapMessage = wiretapSingle.receive(100); assertNotNull(wireTapMessage); - - //There shoud be two messages on this channel. One for 'channel' and one for 'output' + + //There should be three messages on this channel. One for 'channel', one for 'output', and one for 'wiretapSingle'. wireTapMessage = wiretapAll.receive(100); int msgCount=0; while (wireTapMessage != null){ msgCount++; assertEquals(wireTapMessage.getPayload(),message.getPayload()); wireTapMessage = wiretapAll.receive(100); - } - - assertEquals(2,msgCount); + } + + assertEquals(3,msgCount); } - + @Test public void testWireTapsOnRandomChannel(){ - Message message = new GenericMessage("hello"); + Message message = new GenericMessage("hello"); anotherChannel.send(message); - + //This time no message on wiretapSingle Message wireTapMessage = wiretapSingle.receive(100); assertNull(wireTapMessage); - - //There shoud be two messages on this channel. One for 'channel' and one for 'output' + + //There should be two messages on this channel. One for 'channel' and one for 'output' wireTapMessage = wiretapAll.receive(100); int msgCount=0; while (wireTapMessage != null){ msgCount++; assertEquals(wireTapMessage.getPayload(),message.getPayload()); wireTapMessage = wiretapAll.receive(100); - } - + } + assertEquals(2,msgCount); } - + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/ImplicitConsumerChannelTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/ImplicitConsumerChannelTests-context.xml new file mode 100644 index 0000000000..b97b53bb18 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/ImplicitConsumerChannelTests-context.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/ImplicitConsumerChannelTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/ImplicitConsumerChannelTests.java new file mode 100644 index 0000000000..822edba6f8 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/ImplicitConsumerChannelTests.java @@ -0,0 +1,148 @@ +/* + * 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.channel.interceptor; + +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.channel.ChannelInterceptorAware; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Gary Russell + * @since 4.0 + * + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class ImplicitConsumerChannelTests { + + @Autowired + private AbstractMessageChannel bar; + + @Autowired + private AbstractMessageChannel foo; + + @Autowired + private AbstractMessageChannel baz; + + @Test + public void testImplicit() { + // used to fail to load AC (no channel 'bar') + List barInterceptors = bar.getChannelInterceptors(); + assertEquals(2, barInterceptors.size()); + assertThat(barInterceptors.get(0), anyOf(instanceOf(Interceptor1.class), instanceOf(Interceptor2.class))); + assertThat(barInterceptors.get(1), anyOf(instanceOf(Interceptor1.class), instanceOf(Interceptor2.class))); + List fooInterceptors = foo.getChannelInterceptors(); + assertEquals(2, fooInterceptors.size()); + assertThat(fooInterceptors.get(0), anyOf(instanceOf(WireTap.class), instanceOf(Interceptor2.class))); + assertThat(fooInterceptors.get(1), anyOf(instanceOf(WireTap.class), instanceOf(Interceptor2.class))); + List bazInterceptors = baz.getChannelInterceptors(); + assertEquals(2, bazInterceptors.size()); + assertThat(bazInterceptors.get(0), anyOf(instanceOf(WireTap.class), instanceOf(Interceptor1.class))); + assertThat(bazInterceptors.get(1), anyOf(instanceOf(WireTap.class), instanceOf(Interceptor1.class))); + } + + public static class Interceptor1 implements ChannelInterceptor, VetoCapableInterceptor { + + private MessageChannel channel; + + @Override + public Message preSend(Message message, MessageChannel channel) { + return null; + } + + @Override + public void postSend(Message message, MessageChannel channel, boolean sent) { + } + + @Override + public boolean preReceive(MessageChannel channel) { + return false; + } + + @Override + public Message postReceive(Message message, MessageChannel channel) { + return null; + } + + public void setChannel(MessageChannel channel) { + this.channel = channel; + } + + public MessageChannel getChannel() { + return channel; + } + + @Override + public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) { + return !this.channel.equals(channel); + } + + } + + public static class Interceptor2 implements ChannelInterceptor, VetoCapableInterceptor { + + private MessageChannel channel; + + @Override + public Message preSend(Message message, MessageChannel channel) { + return null; + } + + @Override + public void postSend(Message message, MessageChannel channel, boolean sent) { + } + + @Override + public boolean preReceive(MessageChannel channel) { + return false; + } + + @Override + public Message postReceive(Message message, MessageChannel channel) { + return null; + } + + public void setChannel(MessageChannel channel) { + this.channel = channel; + } + + public MessageChannel getChannel() { + return channel; + } + + @Override + public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) { + return !this.channel.equals(channel); + } + + } + +} 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 9bcd29be09..65036f4a7c 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,6 +8,10 @@ - + + + + + diff --git a/src/reference/docbook/channel.xml b/src/reference/docbook/channel.xml index ecf74a480d..96acd7c010 100644 --- a/src/reference/docbook/channel.xml +++ b/src/reference/docbook/channel.xml @@ -837,6 +837,17 @@ payload to an Integer. best of both worlds: 1) the sending of a JMS Message can occur within the transaction while 2) it is still a "fire-and-forget" action thereby preventing any noticeable delay in the main message flow. + + Starting with version 4.0, it is important to avoid circular references when an + interceptor (such as WireTap) references a channel itself. You need to exclude + such channels from those being intercepted by the current interceptor. This can be done with appropriate + patterns or programmatically. If you have a custom ChannelInterceptor + that references a channel, consider implementing VetoCapableInterceptor. + That way, the framework will ask the interceptor if it's OK to intercept each channel that is a candidate based + on the pattern. You can also add runtime protection in the interceptor methods that ensures that the channel is + not one that is referenced by the interceptor. The WireTap uses both of these + techniques. +
Global Wire Tap Configuration diff --git a/src/reference/docbook/redis.xml b/src/reference/docbook/redis.xml index 143c2a83ae..a3fa1b2bda 100644 --- a/src/reference/docbook/redis.xml +++ b/src/reference/docbook/redis.xml @@ -434,7 +434,7 @@ rt.setConnectionFactory(redisConnectionFactory);]]> ]]> - The keys are used to store the data have the form <storeBeanName>:<channelId> + The keys that are used to store the data have the form <storeBeanName>:<channelId> (in the above example, redisMessageStore:somePersistentQueueChannel). diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index b0acab053f..268453841a 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -9,6 +9,13 @@ in more details, please see the Issue Tracker tickets that were resolved as part of the 4.0 development process. + + Please be sure to also see the + Migration Guide for important changes that might affect your applications. + Migration guides for all versions back to 2.1 can be found on the + Wiki. +
New Components