INT-3811: Support Negative Matches on Header Map

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

Polishing - PR Comments
This commit is contained in:
Gary Russell
2016-02-11 12:47:59 -05:00
committed by Artem Bilan
parent 583371007a
commit edb30efcd5
7 changed files with 264 additions and 30 deletions

View File

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

View File

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