INT-3811: Support Negative Matches on Header Map
JIRA: https://jira.spring.io/browse/INT-3811 Polishing - PR Comments
This commit is contained in:
committed by
Artem Bilan
parent
583371007a
commit
edb30efcd5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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,8 +20,8 @@ import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@@ -139,7 +139,7 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
* @return a header mapper that match if any of the specified patters match
|
||||
*/
|
||||
protected HeaderMatcher createHeaderMatcher(Collection<String> patterns) {
|
||||
Collection<HeaderMatcher> matchers = new ArrayList<HeaderMatcher>();
|
||||
List<HeaderMatcher> matchers = new ArrayList<HeaderMatcher>();
|
||||
for (String pattern : patterns) {
|
||||
if (STANDARD_REQUEST_HEADER_NAME_PATTERN.equals(pattern)) {
|
||||
matchers.add(new ContentBasedHeaderMatcher(true, this.requestHeaderNames));
|
||||
@@ -151,7 +151,22 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
matchers.add(new PrefixBasedMatcher(false, this.standardHeaderPrefix));
|
||||
}
|
||||
else {
|
||||
matchers.add(new PatternBasedHeaderMatcher(Collections.singleton(pattern)));
|
||||
String thePattern = pattern;
|
||||
boolean negate = false;
|
||||
if (pattern.startsWith("!")) {
|
||||
thePattern = pattern.substring(1);
|
||||
negate = true;
|
||||
}
|
||||
else if (pattern.startsWith("\\!")) {
|
||||
thePattern = pattern.substring(1);
|
||||
}
|
||||
if (negate) {
|
||||
// negative matchers get priority
|
||||
matchers.add(0, new SinglePatternBasedHeaderMatcher(thePattern, negate));
|
||||
}
|
||||
else {
|
||||
matchers.add(new SinglePatternBasedHeaderMatcher(thePattern, negate));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new CompositeHeaderMatcher(matchers);
|
||||
@@ -343,6 +358,12 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
*/
|
||||
boolean matchHeader(String headerName);
|
||||
|
||||
/**
|
||||
* Return true if this match should be explicitly excluded from the mapping.
|
||||
* @return true if negated.
|
||||
*/
|
||||
boolean isNegated();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,11 +409,16 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNegated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A pattern-based {@link HeaderMatcher} that matches if the specified
|
||||
* header match one of the specified simple patterns.
|
||||
* header matches one of the specified simple patterns.
|
||||
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
|
||||
* @since 4.1
|
||||
*/
|
||||
@@ -400,19 +426,21 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
|
||||
private static final Log logger = LogFactory.getLog(HeaderMatcher.class);
|
||||
|
||||
private final Collection<String> patterns;
|
||||
private final Collection<String> patterns = new ArrayList<String>();
|
||||
|
||||
public PatternBasedHeaderMatcher(Collection<String> patterns) {
|
||||
Assert.notNull(patterns, "Patters must no be null");
|
||||
Assert.notNull(patterns, "Patterns must no be null");
|
||||
Assert.notEmpty(patterns, "At least one pattern must be specified");
|
||||
this.patterns = patterns;
|
||||
for (String pattern : patterns) {
|
||||
this.patterns.add(pattern.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchHeader(String headerName) {
|
||||
String header = headerName.toLowerCase();
|
||||
for (String pattern : this.patterns) {
|
||||
if (PatternMatchUtils.simpleMatch(pattern.toLowerCase(), header)) {
|
||||
if (PatternMatchUtils.simpleMatch(pattern, header)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(MessageFormat.format(
|
||||
"headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern));
|
||||
@@ -423,6 +451,56 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNegated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A pattern-based {@link HeaderMatcher} that matches if the specified
|
||||
* header matches the specified simple pattern.
|
||||
* <p> The {@code negate == true} state indicates if the matching should be treated as "not matched".
|
||||
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
|
||||
* @since 4.3
|
||||
*/
|
||||
protected static class SinglePatternBasedHeaderMatcher implements HeaderMatcher {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(HeaderMatcher.class);
|
||||
|
||||
private final String pattern;
|
||||
|
||||
private final boolean negate;
|
||||
|
||||
public SinglePatternBasedHeaderMatcher(String pattern) {
|
||||
this(pattern, false);
|
||||
}
|
||||
|
||||
public SinglePatternBasedHeaderMatcher(String pattern, boolean negate) {
|
||||
Assert.notNull(pattern, "Pattern must no be null");
|
||||
this.pattern = pattern.toLowerCase();
|
||||
this.negate = negate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchHeader(String headerName) {
|
||||
String header = headerName.toLowerCase();
|
||||
if (PatternMatchUtils.simpleMatch(this.pattern, header)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(MessageFormat.format(
|
||||
"headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNegated() {
|
||||
return this.negate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -457,6 +535,11 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNegated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,14 +547,14 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
* {@link HeaderMatcher}s matches to the {@code headerName}.
|
||||
* @since 4.1
|
||||
*/
|
||||
protected static class CompositeHeaderMatcher implements HeaderMatcher {
|
||||
protected static class CompositeHeaderMatcher implements HeaderMatcher{
|
||||
|
||||
private static final Log logger = LogFactory.getLog(HeaderMatcher.class);
|
||||
|
||||
private final Collection<HeaderMatcher> strategies;
|
||||
private final Collection<HeaderMatcher> matchers;
|
||||
|
||||
CompositeHeaderMatcher(Collection<HeaderMatcher> strategies) {
|
||||
this.strategies = strategies;
|
||||
this.matchers = strategies;
|
||||
}
|
||||
|
||||
CompositeHeaderMatcher(HeaderMatcher... strategies) {
|
||||
@@ -480,8 +563,11 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
|
||||
@Override
|
||||
public boolean matchHeader(String headerName) {
|
||||
for (HeaderMatcher strategy : this.strategies) {
|
||||
for (HeaderMatcher strategy : this.matchers) {
|
||||
if (strategy.matchHeader(headerName)) {
|
||||
if (strategy.isNegated()) {
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -491,6 +577,11 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNegated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
@@ -19,11 +19,6 @@ package org.springframework.integration.mapping;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.springframework.integration.mapping.AbstractHeaderMapper.CompositeHeaderMatcher;
|
||||
import static org.springframework.integration.mapping.AbstractHeaderMapper.ContentBasedHeaderMatcher;
|
||||
import static org.springframework.integration.mapping.AbstractHeaderMapper.HeaderMatcher;
|
||||
import static org.springframework.integration.mapping.AbstractHeaderMapper.PatternBasedHeaderMatcher;
|
||||
import static org.springframework.integration.mapping.AbstractHeaderMapper.PrefixBasedMatcher;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -33,6 +28,12 @@ import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.mapping.AbstractHeaderMapper.CompositeHeaderMatcher;
|
||||
import org.springframework.integration.mapping.AbstractHeaderMapper.ContentBasedHeaderMatcher;
|
||||
import org.springframework.integration.mapping.AbstractHeaderMapper.HeaderMatcher;
|
||||
import org.springframework.integration.mapping.AbstractHeaderMapper.PatternBasedHeaderMatcher;
|
||||
import org.springframework.integration.mapping.AbstractHeaderMapper.PrefixBasedMatcher;
|
||||
import org.springframework.integration.mapping.AbstractHeaderMapper.SinglePatternBasedHeaderMatcher;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -237,6 +238,33 @@ public class HeaderMapperTests {
|
||||
assertEquals(1, properties.getUserDefinedHeaders().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromHeadersToRequestWithStandardRequestPatternAndNegatives() {
|
||||
this.mapper.setRequestHeaderNames("foo", "!foo", "bar", "!baz", "\\!qux", "!fiz*",
|
||||
GenericTestHeaderMapper.STANDARD_REQUEST_HEADER_NAME_PATTERN);
|
||||
Map<String, Object> headers = new HashMap<String, Object>();
|
||||
headers.put(GenericTestHeaders.APP_ID, "myAppId");
|
||||
headers.put(GenericTestHeaders.REDELIVERED, true);
|
||||
headers.put(GenericTestHeaders.REQUEST_ONLY, "request-456");
|
||||
headers.put(GenericTestHeaders.REPLY_ONLY, "reply-456");
|
||||
headers.put("foo", "foo");
|
||||
headers.put("bar", "bar");
|
||||
headers.put("baz", "baz");
|
||||
headers.put("!qux", "qux");
|
||||
headers.put("fizbuz", "fizbuz");
|
||||
MessageHeaders messageHeaders = new MessageHeaders(headers);
|
||||
GenericTestProperties properties = new GenericTestProperties();
|
||||
this.mapper.fromHeadersToRequest(messageHeaders, properties);
|
||||
assertEquals("myAppId", properties.getAppId());
|
||||
assertNull(properties.getTransactionSize());
|
||||
assertEquals(true, properties.getRedelivered());
|
||||
assertEquals("request-456", properties.getRequestOnly());
|
||||
assertNull(properties.getReplyOnly());
|
||||
assertEquals("bar", properties.getUserDefinedHeaders().get("bar"));
|
||||
assertEquals("qux", properties.getUserDefinedHeaders().get("!qux"));
|
||||
assertEquals(2, properties.getUserDefinedHeaders().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromHeadersToReply() {
|
||||
MessageHeaders messageHeaders = createSimpleMessageHeaders();
|
||||
@@ -296,8 +324,9 @@ public class HeaderMapperTests {
|
||||
|
||||
@Test
|
||||
public void prefixHeaderPatternMatching() {
|
||||
@SuppressWarnings("deprecation")
|
||||
PatternBasedHeaderMatcher strategy =
|
||||
new PatternBasedHeaderMatcher(Collections.singleton("foo*"));
|
||||
new PatternBasedHeaderMatcher(Collections.singleton("fOo*"));
|
||||
|
||||
assertMapping(strategy, "foo", true);
|
||||
assertMapping(strategy, "foo123", true);
|
||||
@@ -309,8 +338,35 @@ public class HeaderMapperTests {
|
||||
|
||||
@Test
|
||||
public void suffixHeaderPatternMatching() {
|
||||
@SuppressWarnings("deprecation")
|
||||
PatternBasedHeaderMatcher strategy =
|
||||
new PatternBasedHeaderMatcher(Collections.singleton("*foo"));
|
||||
new PatternBasedHeaderMatcher(Collections.singleton("*fOo"));
|
||||
|
||||
assertMapping(strategy, "foo", true);
|
||||
assertMapping(strategy, "123foo", true);
|
||||
assertMapping(strategy, "FoO", true);
|
||||
|
||||
assertMapping(strategy, "foo123", false);
|
||||
assertMapping(strategy, "foo_", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefixSingleHeaderPatternMatching() {
|
||||
SinglePatternBasedHeaderMatcher strategy =
|
||||
new SinglePatternBasedHeaderMatcher("Foo*");
|
||||
|
||||
assertMapping(strategy, "foo", true);
|
||||
assertMapping(strategy, "foo123", true);
|
||||
assertMapping(strategy, "FoO", true);
|
||||
|
||||
assertMapping(strategy, "123foo", false);
|
||||
assertMapping(strategy, "_foo", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suffixSingleHeaderPatternMatching() {
|
||||
SinglePatternBasedHeaderMatcher strategy =
|
||||
new SinglePatternBasedHeaderMatcher("*fOo");
|
||||
|
||||
assertMapping(strategy, "foo", true);
|
||||
assertMapping(strategy, "123foo", true);
|
||||
@@ -322,7 +378,7 @@ public class HeaderMapperTests {
|
||||
|
||||
@Test
|
||||
public void contentHeaderMatching() {
|
||||
AbstractHeaderMapper.ContentBasedHeaderMatcher strategy =
|
||||
ContentBasedHeaderMatcher strategy =
|
||||
new ContentBasedHeaderMatcher(true, Arrays.asList("foo", "bar"));
|
||||
|
||||
assertMapping(strategy, "foo", true);
|
||||
|
||||
Reference in New Issue
Block a user