diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java index 47340fc76d..a483320b04 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -27,12 +27,15 @@ import java.lang.annotation.Target; * annotation will be applied as global channel interceptors * using the provided {@code patterns} to match channel names. *

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

* This annotation is an analogue of {@code }. * * @author Artem Bilan + * @author Meherzad Lahewala + * * @since 4.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @@ -41,9 +44,12 @@ import java.lang.annotation.Target; 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)}. + * An array of patterns against which channel names will be matched. + * Since version 5.0 negative patterns are also supported. + * A leading '!' negates the pattern match. + * Default is "*" (all channels). * @return The pattern. + * @see org.springframework.integration.util.PatternMatchUtils#smartMatch(String, String...) */ String[] patterns() default "*"; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java index ed21da44da..12753d783c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/GlobalChannelInterceptorProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -37,10 +37,10 @@ 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.integration.util.PatternMatchUtils; 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; /** @@ -50,6 +50,8 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @author Artem Bilan * @author Gary Russell + * @author Meherzad Lahewala + * * @since 2.0 */ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, SmartInitializingSingleton { @@ -59,11 +61,9 @@ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, Smart private final OrderComparator comparator = new OrderComparator(); - private final Set positiveOrderInterceptors = - new LinkedHashSet(); + private final Set positiveOrderInterceptors = new LinkedHashSet<>(); - private final Set negativeOrderInterceptors = - new LinkedHashSet(); + private final Set negativeOrderInterceptors = new LinkedHashSet<>(); private ListableBeanFactory beanFactory; @@ -89,6 +89,7 @@ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, Smart this.negativeOrderInterceptors.add(channelInterceptor); } } + Map channels = this.beanFactory.getBeansOfType(ChannelInterceptorAware.class); for (Entry entry : channels.entrySet()) { @@ -104,15 +105,18 @@ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, Smart if (logger.isDebugEnabled()) { logger.debug("Applying global interceptors on channel '" + beanName + "'"); } - List tempInterceptors = new ArrayList(); + + List tempInterceptors = new ArrayList<>(); for (GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper : this.positiveOrderInterceptors) { String[] patterns = globalChannelInterceptorWrapper.getPatterns(); patterns = StringUtils.trimArrayElements(patterns); - if (PatternMatchUtils.simpleMatch(patterns, beanName)) { + if (beanName != null && Boolean.TRUE.equals(PatternMatchUtils.smartMatch(beanName, patterns))) { tempInterceptors.add(globalChannelInterceptorWrapper); } } + Collections.sort(tempInterceptors, this.comparator); + for (GlobalChannelInterceptorWrapper next : tempInterceptors) { ChannelInterceptor channelInterceptor = next.getChannelInterceptor(); if (!(channelInterceptor instanceof VetoCapableInterceptor) @@ -122,14 +126,17 @@ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, Smart } tempInterceptors.clear(); + for (GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper : this.negativeOrderInterceptors) { String[] patterns = globalChannelInterceptorWrapper.getPatterns(); patterns = StringUtils.trimArrayElements(patterns); - if (PatternMatchUtils.simpleMatch(patterns, beanName)) { + if (beanName != null && Boolean.TRUE.equals(PatternMatchUtils.smartMatch(beanName, patterns))) { tempInterceptors.add(globalChannelInterceptorWrapper); } } + Collections.sort(tempInterceptors, this.comparator); + if (!tempInterceptors.isEmpty()) { for (int i = tempInterceptors.size() - 1; i >= 0; i--) { ChannelInterceptor channelInterceptor = tempInterceptors.get(i).getChannelInterceptor(); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/management/IntegrationManagementConfigurer.java b/spring-integration-core/src/main/java/org/springframework/integration/support/management/IntegrationManagementConfigurer.java index 21ad9d79f9..6a5a15837a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/management/IntegrationManagementConfigurer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/management/IntegrationManagementConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -30,8 +30,8 @@ import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.integration.support.management.IntegrationManagement.ManagementOverrides; +import org.springframework.integration.util.PatternMatchUtils; import org.springframework.util.Assert; -import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; @@ -41,6 +41,8 @@ import org.springframework.util.StringUtils; * * @author Gary Russell * @author Artem Bilan + * @author Meherzad Lahewala + * * @since 4.2 * */ @@ -71,9 +73,9 @@ public class IntegrationManagementConfigurer implements SmartInitializingSinglet private String metricsFactoryBeanName; - private String[] enabledCountsPatterns = { }; + private String[] enabledCountsPatterns = { }; - private String[] enabledStatsPatterns = { }; + private String[] enabledStatsPatterns = { }; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -227,7 +229,7 @@ public class IntegrationManagementConfigurer implements SmartInitializingSinglet AbstractMessageChannelMetrics metrics = this.metricsFactory.createChannelMetrics(name); Assert.state(metrics != null, "'metrics' must not be null"); ManagementOverrides overrides = bean.getOverrides(); - Boolean enabled = smartMatch(this.enabledCountsPatterns, name); + Boolean enabled = PatternMatchUtils.smartMatch(name, this.enabledCountsPatterns); if (enabled != null) { bean.setCountsEnabled(enabled); } @@ -236,7 +238,7 @@ public class IntegrationManagementConfigurer implements SmartInitializingSinglet bean.setCountsEnabled(this.defaultCountsEnabled); } } - enabled = smartMatch(this.enabledStatsPatterns, name); + enabled = PatternMatchUtils.smartMatch(name, this.enabledStatsPatterns); if (enabled != null) { bean.setStatsEnabled(enabled); metrics.setFullStatsEnabled(enabled); @@ -258,7 +260,7 @@ public class IntegrationManagementConfigurer implements SmartInitializingSinglet AbstractMessageHandlerMetrics metrics = this.metricsFactory.createHandlerMetrics(name); Assert.state(metrics != null, "'metrics' must not be null"); ManagementOverrides overrides = bean.getOverrides(); - Boolean enabled = smartMatch(this.enabledCountsPatterns, name); + Boolean enabled = PatternMatchUtils.smartMatch(name, this.enabledCountsPatterns); if (enabled != null) { bean.setCountsEnabled(enabled); } @@ -267,7 +269,7 @@ public class IntegrationManagementConfigurer implements SmartInitializingSinglet bean.setCountsEnabled(this.defaultCountsEnabled); } } - enabled = smartMatch(this.enabledStatsPatterns, name); + enabled = PatternMatchUtils.smartMatch(name, this.enabledStatsPatterns); if (enabled != null) { bean.setStatsEnabled(enabled); metrics.setFullStatsEnabled(enabled); @@ -286,7 +288,7 @@ public class IntegrationManagementConfigurer implements SmartInitializingSinglet } private void configureSourceMetrics(String name, MessageSourceMetrics bean) { - Boolean enabled = smartMatch(this.enabledCountsPatterns, name); + Boolean enabled = PatternMatchUtils.smartMatch(name, this.enabledCountsPatterns); if (enabled != null) { bean.setCountsEnabled(enabled); } @@ -298,33 +300,6 @@ public class IntegrationManagementConfigurer implements SmartInitializingSinglet this.sourcesByName.put(bean.getManagedName() != null ? bean.getManagedName() : name, bean); } - /** - * Simple pattern match against the supplied patterns; also supports negated ('!') - * patterns. First match wins (positive or negative). - * @param patterns the patterns. - * @param name the name to match. - * @return null if no match; true for positive match; false for negative match. - */ - private Boolean smartMatch(String[] patterns, String name) { - if (patterns != null) { - for (String pattern : patterns) { - boolean reverse = false; - String patternToUse = pattern; - if (pattern.startsWith("!")) { - reverse = true; - patternToUse = pattern.substring(1); - } - else if (pattern.startsWith("\\")) { - patternToUse = pattern.substring(1); - } - if (PatternMatchUtils.simpleMatch(patternToUse, name)) { - return !reverse; - } - } - } - return null; //NOSONAR - intentional null return - } - public String[] getChannelNames() { return this.channelsByName.keySet().toArray(new String[this.channelsByName.size()]); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/PatternMatchUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/util/PatternMatchUtils.java new file mode 100644 index 0000000000..2815152e45 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/PatternMatchUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright 2017 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.util; + +/** + * Utility methods for pattern matching. + * This utilities provide support of negative pattern matching as well + * unlike {@link org.springframework.util.PatternMatchUtils}. + * + * @author Meherzad Lahewala + * + * @since 5.0 + * + * @see org.springframework.util.PatternMatchUtils + */ +public final class PatternMatchUtils { + + private PatternMatchUtils() { } + + /** + * Pattern match against the supplied patterns; also supports negated ('!') + * patterns. First match wins (positive or negative). + * To match the names starting with {@code !} symbol, + * you have to escape it prepending with the {@code \} symbol in the pattern definition. + * @param str the string to match. + * @param patterns the patterns. + * @return true for positive match; false for negative; null if no pattern matches. + * @see org.springframework.util.PatternMatchUtils#simpleMatch(String[], String) + */ + public static Boolean smartMatch(String str, String... patterns) { + if (patterns != null) { + for (String pattern : patterns) { + boolean reverse = false; + String patternToUse = pattern; + if (pattern.startsWith("!")) { + reverse = true; + patternToUse = pattern.substring(1); + } + else if (pattern.startsWith("\\")) { + patternToUse = pattern.substring(1); + } + if (org.springframework.util.PatternMatchUtils.simpleMatch(patternToUse, str)) { + return !reverse; + } + } + } + return null; //NOSONAR - intentional null return + } + +} diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.0.xsd index b2894f9ea0..bf082ed31e 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration-5.0.xsd @@ -4349,10 +4349,10 @@ - [REQUIRED] Channel name(s) or patterns. To specify more than one channel use - ','  - (e.g., - channel-name-pattern="input*, foo, bar") + [REQUIRED] Channel name(s) or patterns. To specify more than one channel use ','. + A leading '!' negates the pattern match ('!foo*' means don't add + interceptor where the name matches the pattern 'foo*'). + For example "input*, foo, bar, !Foo*". diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests-context.xml index c3b4ce461f..d4a8ce4a43 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests-context.xml @@ -32,6 +32,11 @@ + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests.java index f382ea89d9..fedc14d512 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/GlobalChannelInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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,13 +16,16 @@ package org.springframework.integration.channel.interceptor; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; import java.util.Map; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,6 +45,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Oleg Zhurakousky * @author David Turanski * @author Artem Bilan + * @author Meherzad Lahewala + * * @since 2.0 */ @RunWith(SpringJUnit4ClassRunner.class) @@ -65,52 +70,63 @@ public class GlobalChannelInterceptorTests { continue; } - ChannelInterceptor[] interceptors = channel.getChannelInterceptors().toArray(new ChannelInterceptor[channel.getChannelInterceptors().size()]); + ChannelInterceptor[] interceptors = channel.getChannelInterceptors() + .toArray(new ChannelInterceptor[channel.getChannelInterceptors().size()]); if (channelName.equals("inputA")) { // 328741 - Assert.assertTrue(interceptors.length == 10); - Assert.assertEquals("interceptor-three", interceptors[0].toString()); - Assert.assertEquals("interceptor-two", interceptors[1].toString()); - Assert.assertEquals("interceptor-eight", interceptors[2].toString()); - Assert.assertEquals("interceptor-seven", interceptors[3].toString()); - Assert.assertEquals("interceptor-five", interceptors[4].toString()); - Assert.assertEquals("interceptor-six", interceptors[5].toString()); - Assert.assertEquals("interceptor-ten", interceptors[6].toString()); - Assert.assertEquals("interceptor-eleven", interceptors[7].toString()); - Assert.assertEquals("interceptor-four", interceptors[8].toString()); - Assert.assertEquals("interceptor-one", interceptors[9].toString()); + assertTrue(interceptors.length == 10); + assertEquals("interceptor-three", interceptors[0].toString()); + assertEquals("interceptor-two", interceptors[1].toString()); + assertEquals("interceptor-eight", interceptors[2].toString()); + assertEquals("interceptor-seven", interceptors[3].toString()); + assertEquals("interceptor-five", interceptors[4].toString()); + assertEquals("interceptor-six", interceptors[5].toString()); + assertEquals("interceptor-ten", interceptors[6].toString()); + assertEquals("interceptor-eleven", interceptors[7].toString()); + assertEquals("interceptor-four", interceptors[8].toString()); + assertEquals("interceptor-one", interceptors[9].toString()); } else if (channelName.equals("inputB")) { - Assert.assertTrue(interceptors.length == 6); - Assert.assertEquals("interceptor-three", interceptors[0].toString()); - Assert.assertEquals("interceptor-two", interceptors[1].toString()); - Assert.assertEquals("interceptor-ten", interceptors[2].toString()); - Assert.assertEquals("interceptor-eleven", interceptors[3].toString()); - Assert.assertEquals("interceptor-four", interceptors[4].toString()); - Assert.assertEquals("interceptor-one", interceptors[5].toString()); + assertTrue(interceptors.length == 6); + assertEquals("interceptor-three", interceptors[0].toString()); + assertEquals("interceptor-two", interceptors[1].toString()); + assertEquals("interceptor-ten", interceptors[2].toString()); + assertEquals("interceptor-eleven", interceptors[3].toString()); + assertEquals("interceptor-four", interceptors[4].toString()); + assertEquals("interceptor-one", interceptors[5].toString()); } else if (channelName.equals("foo")) { - Assert.assertTrue(interceptors.length == 6); - Assert.assertEquals("interceptor-two", interceptors[0].toString()); - Assert.assertEquals("interceptor-five", interceptors[1].toString()); - Assert.assertEquals("interceptor-ten", interceptors[2].toString()); - Assert.assertEquals("interceptor-eleven", interceptors[3].toString()); - Assert.assertEquals("interceptor-four", interceptors[4].toString()); - Assert.assertEquals("interceptor-one", interceptors[5].toString()); + assertTrue(interceptors.length == 6); + assertEquals("interceptor-two", interceptors[0].toString()); + assertEquals("interceptor-five", interceptors[1].toString()); + assertEquals("interceptor-ten", interceptors[2].toString()); + assertEquals("interceptor-eleven", interceptors[3].toString()); + assertEquals("interceptor-four", interceptors[4].toString()); + assertEquals("interceptor-one", interceptors[5].toString()); } else if (channelName.equals("bar")) { - Assert.assertTrue(interceptors.length == 4); - Assert.assertEquals("interceptor-eight", interceptors[0].toString()); - Assert.assertEquals("interceptor-seven", interceptors[1].toString()); - Assert.assertEquals("interceptor-ten", interceptors[2].toString()); - Assert.assertEquals("interceptor-eleven", interceptors[3].toString()); + assertTrue(interceptors.length == 4); + assertEquals("interceptor-eight", interceptors[0].toString()); + assertEquals("interceptor-seven", interceptors[1].toString()); + assertEquals("interceptor-ten", interceptors[2].toString()); + assertEquals("interceptor-eleven", interceptors[3].toString()); } else if (channelName.equals("baz")) { - Assert.assertTrue(interceptors.length == 2); - Assert.assertEquals("interceptor-ten", interceptors[0].toString()); - Assert.assertEquals("interceptor-eleven", interceptors[1].toString()); + assertTrue(interceptors.length == 2); + assertEquals("interceptor-ten", interceptors[0].toString()); + assertEquals("interceptor-eleven", interceptors[1].toString()); } else if (channelName.equals("inputWithProxy")) { - Assert.assertTrue(interceptors.length == 6); + assertTrue(interceptors.length == 6); + } + else if (channelName.equals("test")) { + assertNotNull(interceptors); + assertTrue(interceptors.length == 2); + List interceptorNames = new ArrayList(); + for (ChannelInterceptor interceptor : interceptors) { + interceptorNames.add(interceptor.toString()); + } + assertTrue(interceptorNames.contains("interceptor-ten")); + assertTrue(interceptorNames.contains("interceptor-eleven")); } } } @@ -123,8 +139,8 @@ public class GlobalChannelInterceptorTests { for (ChannelInterceptor interceptor : channelInterceptors) { interceptorNames.add(interceptor.toString()); } - Assert.assertTrue(interceptorNames.contains("interceptor-ten")); - Assert.assertTrue(interceptorNames.contains("interceptor-eleven")); + assertTrue(interceptorNames.contains("interceptor-ten")); + assertTrue(interceptorNames.contains("interceptor-eleven")); } @@ -159,6 +175,7 @@ public class GlobalChannelInterceptorTests { public String toString() { return "interceptor-" + testIdentifier; } + } @@ -174,6 +191,7 @@ public class GlobalChannelInterceptorTests { public void setOrder(int order) { this.order = order; } + } public static class TestInterceptor implements MethodInterceptor { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/GlobalChannelInterceptorProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/GlobalChannelInterceptorProcessorTests.java new file mode 100644 index 0000000000..78c6f4938c --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/GlobalChannelInterceptorProcessorTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2017 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 static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.integration.channel.ChannelInterceptorAware; +import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; +import org.springframework.messaging.support.ChannelInterceptor; + +/** + * @author Meherzad Lahewala + * + * @since 5.0 + */ +public class GlobalChannelInterceptorProcessorTests { + + private GlobalChannelInterceptorProcessor globalChannelInterceptorProcessor; + + private ListableBeanFactory beanFactory; + + @Before + public void setup() { + this.globalChannelInterceptorProcessor = new GlobalChannelInterceptorProcessor(); + this.beanFactory = mock(ListableBeanFactory.class); + this.globalChannelInterceptorProcessor.setBeanFactory(this.beanFactory); + } + + @Test + public void testProcessorWithNoInterceptor() { + when(this.beanFactory.getBeansOfType(GlobalChannelInterceptorWrapper.class)) + .thenReturn(Collections.emptyMap()); + this.globalChannelInterceptorProcessor.afterSingletonsInstantiated(); + verify(this.beanFactory) + .getBeansOfType(GlobalChannelInterceptorWrapper.class); + verify(this.beanFactory, Mockito.never()) + .getBeansOfType(ChannelInterceptorAware.class); + } + + @Test + public void testProcessorWithInterceptorDefaultPattern() { + Map interceptors = new HashMap<>(); + Map channels = new HashMap<>(); + ChannelInterceptor channelInterceptor = Mockito.mock(ChannelInterceptor.class); + GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper = + new GlobalChannelInterceptorWrapper(channelInterceptor); + + ChannelInterceptorAware channel = Mockito.mock(ChannelInterceptorAware.class); + + interceptors.put("Test-1", globalChannelInterceptorWrapper); + channels.put("Test-1", channel); + when(this.beanFactory.getBeansOfType(GlobalChannelInterceptorWrapper.class)) + .thenReturn(interceptors); + when(this.beanFactory.getBeansOfType(ChannelInterceptorAware.class)) + .thenReturn(channels); + + this.globalChannelInterceptorProcessor.afterSingletonsInstantiated(); + + verify(channel) + .addInterceptor(channelInterceptor); + } + + @Test + public void testProcessorWithInterceptorMatchingPattern() { + Map interceptors = new HashMap<>(); + Map channels = new HashMap<>(); + ChannelInterceptor channelInterceptor = Mockito.mock(ChannelInterceptor.class); + GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper = + new GlobalChannelInterceptorWrapper(channelInterceptor); + + ChannelInterceptorAware channel = Mockito.mock(ChannelInterceptorAware.class); + + globalChannelInterceptorWrapper.setPatterns(new String[] { "Te*" }); + interceptors.put("Test-1", globalChannelInterceptorWrapper); + channels.put("Test-1", channel); + when(this.beanFactory.getBeansOfType(GlobalChannelInterceptorWrapper.class)) + .thenReturn(interceptors); + when(this.beanFactory.getBeansOfType(ChannelInterceptorAware.class)) + .thenReturn(channels); + this.globalChannelInterceptorProcessor.afterSingletonsInstantiated(); + + verify(channel) + .addInterceptor(channelInterceptor); + } + + @Test + public void testProcessorWithInterceptorNotMatchingPattern() { + Map interceptors = new HashMap<>(); + Map channels = new HashMap<>(); + ChannelInterceptor channelInterceptor = Mockito.mock(ChannelInterceptor.class); + GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper = + new GlobalChannelInterceptorWrapper(channelInterceptor); + + ChannelInterceptorAware channel = Mockito.mock(ChannelInterceptorAware.class); + + globalChannelInterceptorWrapper.setPatterns(new String[] { "te*" }); + interceptors.put("Test-1", globalChannelInterceptorWrapper); + channels.put("Test-1", channel); + when(this.beanFactory.getBeansOfType(GlobalChannelInterceptorWrapper.class)) + .thenReturn(interceptors); + when(this.beanFactory.getBeansOfType(ChannelInterceptorAware.class)) + .thenReturn(channels); + + this.globalChannelInterceptorProcessor.afterSingletonsInstantiated(); + + verify(channel, Mockito.never()) + .addInterceptor(channelInterceptor); + } + + @Test + public void testProcessorWithInterceptorMatchingNegativePattern() { + Map interceptors = new HashMap<>(); + Map channels = new HashMap<>(); + ChannelInterceptor channelInterceptor = Mockito.mock(ChannelInterceptor.class); + GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper = + new GlobalChannelInterceptorWrapper(channelInterceptor); + + ChannelInterceptorAware channel = Mockito.mock(ChannelInterceptorAware.class); + + globalChannelInterceptorWrapper.setPatterns(new String[] { "!te*", "!Te*" }); + interceptors.put("Test-1", globalChannelInterceptorWrapper); + channels.put("Test-1", channel); + when(this.beanFactory.getBeansOfType(GlobalChannelInterceptorWrapper.class)) + .thenReturn(interceptors); + when(this.beanFactory.getBeansOfType(ChannelInterceptorAware.class)) + .thenReturn(channels); + this.globalChannelInterceptorProcessor.afterSingletonsInstantiated(); + + verify(channel, Mockito.never()) + .addInterceptor(channelInterceptor); + } + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java index 6ffde3c31c..dbc71961d2 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -69,6 +69,7 @@ import org.springframework.integration.support.management.RouterMetrics; import org.springframework.integration.support.management.Statistics; import org.springframework.integration.support.management.TrackableComponent; import org.springframework.integration.support.management.TrackableRouterMetrics; +import org.springframework.integration.util.PatternMatchUtils; import org.springframework.jmx.export.MBeanExporter; import org.springframework.jmx.export.UnableToRegisterMBeanException; import org.springframework.jmx.export.annotation.ManagedAttribute; @@ -79,7 +80,6 @@ import org.springframework.jmx.support.MetricType; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import org.springframework.util.Assert; -import org.springframework.util.PatternMatchUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringValueResolver; @@ -109,6 +109,7 @@ import org.springframework.util.StringValueResolver; * @author Oleg Zhurakousky * @author Gary Russell * @author Artem Bilan + * @author Meherzad Lahewala */ @org.springframework.jmx.export.annotation.ManagedResource public class IntegrationMBeanExporter extends MBeanExporter implements ApplicationContextAware, @@ -721,37 +722,10 @@ public class IntegrationMBeanExporter extends MBeanExporter implements Applicati * @return true if positive match, false if no match or negative match. */ private boolean matches(String[] patterns, String name) { - Boolean match = smartMatch(patterns, name); + Boolean match = PatternMatchUtils.smartMatch(name, patterns); return match == null ? false : match; } - /** - * Simple pattern match against the supplied patterns; also supports negated ('!') - * patterns. First match wins (positive or negative). - * @param patterns the patterns. - * @param name the name to match. - * @return null if no match; true for positive match; false for negative match. - */ - private Boolean smartMatch(String[] patterns, String name) { - if (patterns != null) { - for (String pattern : patterns) { - boolean reverse = false; - String patternToUse = pattern; - if (pattern.startsWith("!")) { - reverse = true; - patternToUse = pattern.substring(1); - } - else if (pattern.startsWith("\\")) { - patternToUse = pattern.substring(1); - } - if (PatternMatchUtils.simpleMatch(patternToUse, name)) { - return !reverse; - } - } - } - return null; //NOSONAR - intentional null return - } - private Object extractTarget(Object bean) { if (!(bean instanceof Advised)) { return bean; diff --git a/src/reference/asciidoc/channel.adoc b/src/reference/asciidoc/channel.adoc index d6081281f5..f679c6f004 100644 --- a/src/reference/asciidoc/channel.adoc +++ b/src/reference/asciidoc/channel.adoc @@ -691,7 +691,7 @@ To avoid repeated configuration while also enabling interceptors to apply to mul Look at the example below: [source,xml] ---- - + ---- @@ -700,13 +700,19 @@ or [source,xml] ---- - + ---- Each `` element allows you to define a global interceptor which will be applied on all channels that match any patterns defined via the `pattern` attribute. -In the above case the global interceptor will be applied on the 'foo' channel and all other channels that begin with 'bar' or 'input'. +In the above case the global interceptor will be applied on the 'foo' channel and all other channels that begin with 'bar' or 'input' and not to channel starting with 'baz' (starting with _version 5.0_). + +WARNING: The addition of this syntax to the pattern causes one possible (although perhaps unlikely) problem. +If you have a bean `"!foo"`*and* you included a pattern `"!foo"` in your channel-interceptor's `pattern` patterns; it will no long match; the pattern will now match all beans *not* named `foo`. +In this case, you can escape the `!` in the pattern with `\`. +The pattern `"\!foo"` means match a bean named `"!foo"`. + The _order_ attribute allows you to manage where this interceptor will be injected if there are multiple interceptors on a given channel. For example, channel 'inputChannel' could have individual interceptors configured locally (see below): [source,xml] @@ -718,7 +724,8 @@ For example, channel 'inputChannel' could have individual interceptors configure ---- -A reasonable question is how will a global interceptor be injected in relation to other interceptors configured locally or through other global interceptor definitions? The current implementation provides a very simple mechanism for defining the order of interceptor execution. +A reasonable question is how will a global interceptor be injected in relation to other interceptors configured locally or through other global interceptor definitions? +The current implementation provides a very simple mechanism for defining the order of interceptor execution. A positive number in the `order` attribute will ensure interceptor injection after any existing interceptors and a negative number will ensure that the interceptor is injected before existing interceptors. This means that in the above example, the global interceptor will be injected _AFTER_ (since its order is greater than 0) the 'wire-tap' interceptor configured locally. If there were another global interceptor with a matching `pattern`, its order would be determined by comparing the values of the `order` attribute. diff --git a/src/reference/asciidoc/jmx.adoc b/src/reference/asciidoc/jmx.adoc index d9110c2041..24e2467b79 100644 --- a/src/reference/asciidoc/jmx.adoc +++ b/src/reference/asciidoc/jmx.adoc @@ -387,7 +387,7 @@ i.e. `"!foo*, foox"` will match all beans that don't start with `foo`, except `foox`. Patterns are evaluated left to right and the first match (positive or negative) wins and no further patterns are applied. -WARNING: The addition of this syntax to the pattern causes one possible (although perhaps unlikey) problem. +WARNING: The addition of this syntax to the pattern causes one possible (although perhaps unlikely) problem. If you have a bean `"!foo"`*and* you included a pattern `"!foo"` in your MBean exporter's `managed-components` patterns; it will no long match; the pattern will now match all beans *not* named `foo`. In this case, you can escape the `!` in the pattern with `\`. The pattern `"\!foo"` means match a bean named `"!foo"`. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 07aab180e9..56c9da63ff 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -272,3 +272,9 @@ You can now configure the TCP connection factories to support `PushbackInputStre A `ByteArrayElasticRawDeserializer` has been added without `maxMessageSize` control and buffer incoming data as needed. See <> for more information. + +==== GlobalChannelInterceptor changes + +The `@GlobalChannelInterceptor` annotation and `` now support negative patterns (via `!` prepending) for component names matching. + +See <> for more information.