INT-3936: GlobalChIntercep: add negative pattern

JIRA: https://jira.spring.io/browse/INT-3936

* Extract `smartMatch()` logic from the `IntegrationManagementConfigurer`
and `IntegrationMBeanExporter` into the `PatternMatchUtils` class
* Add negative (`!`) pattern matching configuration support to the
`GlobalChannelInterceptor` annotation and `<int:channel-interceptor>` component
* Code style, Docs and JavaDocs polishing
This commit is contained in:
Meherzad Lahewala
2017-09-13 00:41:53 +05:30
committed by Artem Bilan
parent 671097fd50
commit 40783ff547
12 changed files with 347 additions and 127 deletions

View File

@@ -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.
* <p>
* 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}.
* <p>
* This annotation is an analogue of {@code <int:channel-interceptor/>}.
*
* @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 "*";

View File

@@ -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<GlobalChannelInterceptorWrapper> positiveOrderInterceptors =
new LinkedHashSet<GlobalChannelInterceptorWrapper>();
private final Set<GlobalChannelInterceptorWrapper> positiveOrderInterceptors = new LinkedHashSet<>();
private final Set<GlobalChannelInterceptorWrapper> negativeOrderInterceptors =
new LinkedHashSet<GlobalChannelInterceptorWrapper>();
private final Set<GlobalChannelInterceptorWrapper> negativeOrderInterceptors = new LinkedHashSet<>();
private ListableBeanFactory beanFactory;
@@ -89,6 +89,7 @@ final class GlobalChannelInterceptorProcessor implements BeanFactoryAware, Smart
this.negativeOrderInterceptors.add(channelInterceptor);
}
}
Map<String, ChannelInterceptorAware> channels =
this.beanFactory.getBeansOfType(ChannelInterceptorAware.class);
for (Entry<String, ChannelInterceptorAware> 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<GlobalChannelInterceptorWrapper> tempInterceptors = new ArrayList<GlobalChannelInterceptorWrapper>();
List<GlobalChannelInterceptorWrapper> 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();

View File

@@ -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()]);
}

View File

@@ -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
}
}

View File

@@ -4349,10 +4349,10 @@
<xsd:attribute name="pattern" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
[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*".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -32,6 +32,11 @@
<int:channel id="baz"/>
<int:channel id="test"/>
<int:channel-interceptor pattern="!tes*" order="8">
<bean class="org.springframework.integration.channel.interceptor.GlobalChannelInterceptorTests$SampleInterceptor" p:testIdentifier="twelve"/>
</int:channel-interceptor>
<int:channel-interceptor pattern="input*, foo" order="3">
<bean class="org.springframework.integration.channel.interceptor.GlobalChannelInterceptorTests$SampleInterceptor" p:testIdentifier="one"/>
</int:channel-interceptor>

View File

@@ -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<String> interceptorNames = new ArrayList<String>();
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 {

View File

@@ -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<String, GlobalChannelInterceptorWrapper> interceptors = new HashMap<>();
Map<String, ChannelInterceptorAware> 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<String, GlobalChannelInterceptorWrapper> interceptors = new HashMap<>();
Map<String, ChannelInterceptorAware> 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<String, GlobalChannelInterceptorWrapper> interceptors = new HashMap<>();
Map<String, ChannelInterceptorAware> 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<String, GlobalChannelInterceptorWrapper> interceptors = new HashMap<>();
Map<String, ChannelInterceptorAware> 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);
}
}

View File

@@ -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;

View File

@@ -691,7 +691,7 @@ To avoid repeated configuration while also enabling interceptors to apply to mul
Look at the example below:
[source,xml]
----
<int:channel-interceptor pattern="input*, bar*, foo" order="3">
<int:channel-interceptor pattern="input*, bar*, foo, !baz*" order="3">
<bean class="foo.barSampleInterceptor"/>
</int:channel-interceptor>
----
@@ -700,13 +700,19 @@ or
[source,xml]
----
<int:channel-interceptor ref="myInterceptor" pattern="input*, bar*, foo" order="3"/>
<int:channel-interceptor ref="myInterceptor" pattern="input*, bar*, foo, !baz*" order="3"/>
<bean id="myInterceptor" class="foo.barSampleInterceptor"/>
----
Each `<channel-interceptor/>` 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
</int:channel>
----
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.

View File

@@ -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"`.

View File

@@ -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 <<ip>> for more information.
==== GlobalChannelInterceptor changes
The `@GlobalChannelInterceptor` annotation and `<int:channel-interceptor>` now support negative patterns (via `!` prepending) for component names matching.
See <<global-channel-configuration-interceptors>> for more information.