From 463c185b380e7827bc1666ff9c5f83ee0413b3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 19 Aug 2014 12:01:10 +0300 Subject: [PATCH] Refactor AbstractHeaderMapper This commit updates AbstractHeaderMapper in a number of ways: * The 'userDefinedHeaderPrefix' property has been removed as some advanced tests revealed that the feature is actually broken right now and there is no easy way to fix it. A new richer hook point that indicates if the property comes from the target object or the standard MessageHeaders has been added. * A HeaderMatcher interface has been introduced to replace the lengthy checks in shouldMapHeader. Several implementations of that interface are provided * An additional pattern has been added that maps any header that is *not* a standard header. It uses the standardHeaderPrefix property for that purpose * A number of protected method that were only used to create the instance have been removed in favour of a non default constructor. Subclasses should provide those *static* values in their own default constructor. * The list of transient headers can now be customized. Transient headers are headers that should never be mapped. The standard ErrorChannel and ReplyChannel headers have also been removed from the default transient headers list as they no longer need to be transient header-mapper: Polishing --- .../amqp/support/DefaultAmqpHeaderMapper.java | 23 +- .../config/spring-integration-amqp-4.1.xsd | 8 +- .../support/DefaultAmqpHeaderMapperTests.java | 25 +- .../mapping/AbstractHeaderMapper.java | 479 ++++++++++------ .../mapping/RequestReplyHeaderMapper.java | 29 +- .../mapping/HeaderMapperTests.java | 535 ++++++++++++++++++ .../file/remote/RemoteFileOperations.java | 1 + .../ws/DefaultSoapHeaderMapper.java | 19 +- .../WebServiceInboundGatewayParserTests.java | 28 +- .../WebServiceOutboundGatewayParserTests.java | 21 +- ...eOutboundGatewayWithHeaderMapperTests.java | 26 +- .../xmpp/support/DefaultXmppHeaderMapper.java | 19 +- .../config/spring-integration-xmpp-4.1.xsd | 2 +- ...sageOutboundChannelAdapterParserTests.java | 17 +- src/reference/docbook/amqp.xml | 11 +- src/reference/docbook/whats-new.xml | 8 + 16 files changed, 988 insertions(+), 263 deletions(-) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/mapping/HeaderMapperTests.java diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java index f8119f59dd..8f36d38e55 100644 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java +++ b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.java @@ -48,6 +48,7 @@ import org.springframework.util.StringUtils; * @author Oleg Zhurakousky * @author Gary Russell * @author Artem Bilan + * @author Stephane Nicoll * @since 2.1 */ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper implements AmqpHeaderMapper { @@ -80,6 +81,10 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper getStandardRequestHeaderNames() { - return STANDARD_HEADER_NAMES; - } - - - @Override - protected List getStandardReplyHeaderNames() { - return STANDARD_HEADER_NAMES; - } - - - @Override - protected String getStandardHeaderPrefix() { - return AmqpHeaders.PREFIX; - } - /** * Will extract Content-Type from MessageHeaders and convert it to String if possible * Required since Content-Type can be represented as org.springframework.http.MediaType diff --git a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp-4.1.xsd b/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp-4.1.xsd index d1205b3823..d259a4a55a 100644 --- a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp-4.1.xsd +++ b/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp-4.1.xsd @@ -195,7 +195,7 @@ this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo"). A special token 'STANDARD_REPLY_HEADERS' represents all the standard AMQP headers (replyTo, correlationId etc); it is included by default. If you wish to add your own headers, you must also include this token if you wish the - standard headers to also be mapped. + standard headers to also be mapped. To map all non-standard headers the 'NON_STANDARD_HEADERS' token can be used. ]]> @@ -257,7 +257,7 @@ this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo"). A special token 'STANDARD_REPLY_HEADERS' represents all the standard AMQP headers (replyTo, correlationId etc); it is included by default. If you wish to add your own headers, you must also include this token if you wish the - standard headers to also be mapped. + standard headers to also be mapped. To map all non-standard headers the 'NON_STANDARD_HEADERS' token can be used. ]]> @@ -457,7 +457,7 @@ This can only be provided if the 'header-mapper' reference is not being set dire this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo"). A special token 'STANDARD_REQUEST_HEADERS' represents all the standard AMQP headers (replyTo, correlationId etc); it is included by default. If you wish to add your own headers, you must also include this token if you wish the -standard headers to also be mapped. +standard headers to also be mapped. To map all non-standard headers the 'NON_STANDARD_HEADERS' token can be used. ]]> @@ -559,7 +559,7 @@ This can only be provided if the 'header-mapper' reference is not being set dire this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo"). A special token 'STANDARD_REQUEST_HEADERS' represents all the standard AMQP headers (replyTo, correlationId etc); it is included by default. If you wish to add your own headers, you must also include this token if you wish the -standard headers to also be mapped. +standard headers to also be mapped. To map all non-standard headers the 'NON_STANDARD_HEADERS' token can be used. ]]> diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java index de29700bb7..f283744d5d 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,9 @@ package org.springframework.integration.amqp.support; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; import java.util.Date; import java.util.HashMap; @@ -30,6 +32,7 @@ import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.support.converter.JsonMessageConverter; import org.springframework.http.MediaType; +import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; import org.springframework.integration.amqp.AmqpHeaders; @@ -37,6 +40,7 @@ import org.springframework.integration.amqp.AmqpHeaders; * @author Mark Fisher * @author Gary Russell * @author Oleg Zhurakousky + * @author Stephane Nicoll * @since 2.1 */ public class DefaultAmqpHeaderMapperTests { @@ -67,6 +71,10 @@ public class DefaultAmqpHeaderMapperTests { headerMap.put(AmqpHeaders.USER_ID, "test.userId"); headerMap.put(AmqpHeaders.SPRING_REPLY_CORRELATION, "test.correlation"); headerMap.put(AmqpHeaders.SPRING_REPLY_TO_STACK, "test.replyTo2"); + + headerMap.put(MessageHeaders.ERROR_CHANNEL, mock(MessageChannel.class)); + headerMap.put(MessageHeaders.REPLY_CHANNEL, mock(MessageChannel.class)); + MessageHeaders integrationHeaders = new MessageHeaders(headerMap); MessageProperties amqpProperties = new MessageProperties(); headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); @@ -95,6 +103,9 @@ public class DefaultAmqpHeaderMapperTests { assertEquals("test.userId", amqpProperties.getUserId()); assertEquals("test.correlation", amqpProperties.getHeaders().get(AmqpHeaders.STACKED_CORRELATION_HEADER)); assertEquals("test.replyTo2", amqpProperties.getHeaders().get(AmqpHeaders.STACKED_REPLY_TO_HEADER)); + + assertNull(amqpProperties.getHeaders().get(MessageHeaders.ERROR_CHANNEL)); + assertNull(amqpProperties.getHeaders().get(MessageHeaders.REPLY_CHANNEL)); } @Test @@ -163,25 +174,25 @@ public class DefaultAmqpHeaderMapperTests { } @Test - public void replyChannelNotMappedToAmqpProperties() { + public void messageIdNotMappedToAmqpProperties() { DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper(); Map headerMap = new HashMap(); - headerMap.put(MessageHeaders.REPLY_CHANNEL, "foo"); + headerMap.put(MessageHeaders.ID, "msg-id"); MessageHeaders integrationHeaders = new MessageHeaders(headerMap); MessageProperties amqpProperties = new MessageProperties(); headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - assertEquals(null, amqpProperties.getHeaders().get(MessageHeaders.REPLY_CHANNEL)); + assertNull(amqpProperties.getHeaders().get(MessageHeaders.ID)); } @Test - public void errorChannelNotMappedToAmqpProperties() { + public void messageTimestampNotMappedToAmqpProperties() { DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper(); Map headerMap = new HashMap(); - headerMap.put(MessageHeaders.ERROR_CHANNEL, "foo"); + headerMap.put(MessageHeaders.TIMESTAMP, 1234L); MessageHeaders integrationHeaders = new MessageHeaders(headerMap); MessageProperties amqpProperties = new MessageProperties(); headerMapper.fromHeadersToRequest(integrationHeaders, amqpProperties); - assertEquals(null, amqpProperties.getHeaders().get(MessageHeaders.ERROR_CHANNEL)); + assertNull(amqpProperties.getHeaders().get(MessageHeaders.TIMESTAMP)); } @Test // INT-2090 diff --git a/spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java index b97b4f03d3..1578603f25 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/mapping/AbstractHeaderMapper.java @@ -19,152 +19,168 @@ package org.springframework.integration.mapping; 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.integration.mapping.support.JsonHeaders; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; /** - * Abstract base class for HeaderMapper implementations. + * Abstract base class for {@link RequestReplyHeaderMapper} implementations. * * @author Mark Fisher * @author Oleg Zhurakousky + * @author Stephane Nicoll * @since 2.1 */ public abstract class AbstractHeaderMapper implements RequestReplyHeaderMapper { + /** + * A special pattern that only matches standard request headers. + */ public static final String STANDARD_REQUEST_HEADER_NAME_PATTERN = "STANDARD_REQUEST_HEADERS"; + /** + * A special pattern that only matches standard reply headers. + */ public static final String STANDARD_REPLY_HEADER_NAME_PATTERN = "STANDARD_REPLY_HEADERS"; - private static final String[] TRANSIENT_HEADER_NAMES = new String[] { - MessageHeaders.ID, - MessageHeaders.ERROR_CHANNEL, - MessageHeaders.REPLY_CHANNEL, - MessageHeaders.TIMESTAMP - }; + /** + * A special pattern that matches any header that is not a standard header (i.e. any + * header that does not start with the configured standard header prefix) + */ + public static final String NON_STANDARD_HEADER_NAME_PATTERN = "NON_STANDARD_HEADERS"; - protected final Log logger = LogFactory.getLog(this.getClass()); + private static final Collection TRANSIENT_HEADER_NAMES = Arrays.asList( + MessageHeaders.ID, MessageHeaders.TIMESTAMP); + + protected final Log logger = LogFactory.getLog(getClass()); private final String standardHeaderPrefix; - private volatile String userDefinedHeaderPrefix = ""; + private final Collection requestHeaderNames; - private volatile List requestHeaderNames = new ArrayList(); + private final Collection replyHeaderNames; - private volatile List replyHeaderNames = new ArrayList(); + private volatile HeaderMatcher requestHeaderMatcher; - protected AbstractHeaderMapper() { - this.standardHeaderPrefix = this.getStandardHeaderPrefix(); - this.requestHeaderNames.addAll(this.getStandardRequestHeaderNames()); - this.replyHeaderNames.addAll(this.getStandardReplyHeaderNames()); + private volatile HeaderMatcher replyHeaderMatcher; + + /** + * Create a new instance. + * @param standardHeaderPrefix the header prefix that identifies standard header. Such prefix helps to + * differentiate user-defined headers from standard headers. If set, user-defined headers are also + * mapped by default + * @param requestHeaderNames the header names that should be mapped from a request to {@link MessageHeaders} + * @param replyHeaderNames the header names that should be mapped to a response from {@link MessageHeaders} + */ + protected AbstractHeaderMapper(String standardHeaderPrefix, + Collection requestHeaderNames, Collection replyHeaderNames) { + + this.standardHeaderPrefix = standardHeaderPrefix; + this.requestHeaderNames = requestHeaderNames; + this.replyHeaderNames = replyHeaderNames; + this.requestHeaderMatcher = createDefaultHeaderMatcher(this.standardHeaderPrefix, this.requestHeaderNames); + this.replyHeaderMatcher = createDefaultHeaderMatcher(this.standardHeaderPrefix, this.replyHeaderNames); } /** - * Provide the header names that should be mapped from a request (for inbound/outbound adapters) - * TO a Spring Integration Message's headers. - * The values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched. - *

- * This will match the header name directly or, for non-standard headers, it will match - * the header name prefixed with the value, if specified, by {@link #setUserDefinedHeaderPrefix(String)}. - * + * Provide the header names that should be mapped from a request + * to a {@link MessageHeaders}. + *

The values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched. * @param requestHeaderNames The request header names. */ - public void setRequestHeaderNames(String[] requestHeaderNames) { + public void setRequestHeaderNames(String... requestHeaderNames) { Assert.notNull(requestHeaderNames, "'requestHeaderNames' must not be null"); - this.requestHeaderNames = Arrays.asList(requestHeaderNames); + this.requestHeaderMatcher = createHeaderMatcher(Arrays.asList(requestHeaderNames)); } /** - * Provide the header names that should be mapped to a response (for inbound/outbound adapters) - * FROM a Spring Integration Message's headers. - * The values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched. - *

- * Any non-standard headers will be prefixed with the value specified by {@link #setUserDefinedHeaderPrefix(String)}. + * Provide the header names that should be mapped to a response + * from a {@link MessageHeaders}. + *

The values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched. * * @param replyHeaderNames The reply header names. */ - public void setReplyHeaderNames(String[] replyHeaderNames) { + public void setReplyHeaderNames(String... replyHeaderNames) { Assert.notNull(replyHeaderNames, "'replyHeaderNames' must not be null"); - this.replyHeaderNames = Arrays.asList(replyHeaderNames); + this.replyHeaderMatcher = createHeaderMatcher(Arrays.asList(replyHeaderNames)); } /** - * Specify a prefix to be prepended to the header name for any integration - * message header that is being mapped to or from a user-defined value. - *

- * This does not affect the standard properties for the particular protocol, such as - * contentType for AMQP, etc. The header names used for mapping such properties are - * defined in a corresponding Headers class as constants (e.g. AmqpHeaders). - * - * @param userDefinedHeaderPrefix The user defined header prefix. + * Create the initial {@link HeaderMatcher} based on the specified headers and + * standard header prefix. + * @param standardHeaderPrefix the prefix for standard headers. + * @param headerNames the collection of header names to map. + * @return the deafault {@link HeaderMatcher} instance. */ - public void setUserDefinedHeaderPrefix(String userDefinedHeaderPrefix) { - this.userDefinedHeaderPrefix = (userDefinedHeaderPrefix != null) ? userDefinedHeaderPrefix : ""; + protected HeaderMatcher createDefaultHeaderMatcher(String standardHeaderPrefix, Collection headerNames) { + return new ContentBasedHeaderMatcher(true, headerNames); } /** - * Maps headers from a Spring Integration MessageHeaders instance to the target instance - * matching on the set of REQUEST headers (if different). - * - * @param headers The headers. - * @param target The target. + * Create a {@link HeaderMatcher} that match if any of the specified {@code patterns} + * match. The pattern can be a header name, a wildcard pattern such as + * {@code foo*}, {@code *foo}, or {@code within*foo}. + *

Special patterns are also recognized: {@link #STANDARD_REQUEST_HEADER_NAME_PATTERN}, + * {@link #STANDARD_REQUEST_HEADER_NAME_PATTERN} and {@link #NON_STANDARD_HEADER_NAME_PATTERN}. + * @param patterns the patterns to apply + * @return a header mapper that match if any of the specified patters match */ + protected HeaderMatcher createHeaderMatcher(Collection patterns) { + Collection matchers = new ArrayList(); + for (String pattern : patterns) { + if (STANDARD_REQUEST_HEADER_NAME_PATTERN.equals(pattern)) { + matchers.add(new ContentBasedHeaderMatcher(true, this.requestHeaderNames)); + } + else if (STANDARD_REPLY_HEADER_NAME_PATTERN.equals(pattern)) { + matchers.add(new ContentBasedHeaderMatcher(true, this.replyHeaderNames)); + } + else if (NON_STANDARD_HEADER_NAME_PATTERN.equals(pattern)) { + matchers.add(new PrefixBasedMatcher(false, this.standardHeaderPrefix)); + } + else { + matchers.add(new PatternBasedHeaderMatcher(Collections.singleton(pattern))); + } + } + return new CompositeHeaderMatcher(matchers); + } + @Override public void fromHeadersToRequest(MessageHeaders headers, T target) { - this.fromHeaders(headers, target, this.requestHeaderNames); - } - /** - * Maps headers from a Spring Integration MessageHeaders instance to the target instance - * matching on the set of REPLY headers (if different). - * - * @param headers The headers. - * @param target The target. - */ - @Override - public void fromHeadersToReply(MessageHeaders headers, T target) { - this.fromHeaders(headers, target, this.replyHeaderNames); - } - /** - * Maps headers/properties of the target object to Map of MessageHeaders - * matching on the set of REQUEST headers - * - * @param source The source. - * @return The headers. - */ - @Override - public Map toHeadersFromRequest(T source) { - return this.toHeaders(source, this.requestHeaderNames); - } - /** - * Maps headers/properties of the target object to Map of MessageHeaders - * matching on the set of REPLY headers - * - * @param source The source. - * @return The headers. - */ - @Override - public Map toHeadersFromReply(T source) { - return this.toHeaders(source, this.replyHeaderNames); + this.fromHeaders(headers, target, this.requestHeaderMatcher); } - private void fromHeaders(MessageHeaders headers, T target, List headerPatterns){ + @Override + public void fromHeadersToReply(MessageHeaders headers, T target) { + this.fromHeaders(headers, target, this.replyHeaderMatcher); + } + + @Override + public Map toHeadersFromRequest(T source) { + return this.toHeaders(source, this.requestHeaderMatcher); + } + + @Override + public Map toHeadersFromReply(T source) { + return this.toHeaders(source, this.replyHeaderMatcher); + } + + private void fromHeaders(MessageHeaders headers, T target, HeaderMatcher headerMatcher) { try { Map subset = new HashMap(); - for (String headerName : headers.keySet()) { - if (this.shouldMapHeader(headerName, headerPatterns)){ - subset.put(headerName, headers.get(headerName)); + for (Map.Entry entry : headers.entrySet()) { + String headerName = entry.getKey(); + if (this.shouldMapHeader(headerName, headerMatcher)) { + subset.put(headerName, entry.getValue()); } } this.populateStandardHeaders(subset, target); @@ -182,8 +198,8 @@ public abstract class AbstractHeaderMapper implements RequestReplyHeaderMappe Object value = headers.get(headerName); if (value != null) { try { - if (!headerName.startsWith(this.standardHeaderPrefix)){ - String key = this.addPrefixIfNecessary(this.userDefinedHeaderPrefix, headerName); + if (!headerName.startsWith(this.standardHeaderPrefix)) { + String key = this.createTargetPropertyName(headerName, true); this.populateUserDefinedHeader(key, value, target); } } @@ -197,24 +213,24 @@ public abstract class AbstractHeaderMapper implements RequestReplyHeaderMappe } /** - * Maps headers from a source instance to the MessageHeaders of a - * Spring Integration Message. + * Map headers from a source instance to the {@link MessageHeaders} of + * a {@link org.springframework.messaging.Message}. */ - private Map toHeaders(T source, List headerPatterns) { + private Map toHeaders(T source, HeaderMatcher headerMatcher) { Map headers = new HashMap(); - Map standardHeaders = this.extractStandardHeaders(source); - this.copyHeaders(this.standardHeaderPrefix, standardHeaders, headers, headerPatterns); - Map userDefinedHeaders = this.extractUserDefinedHeaders(source); - this.copyHeaders(this.userDefinedHeaderPrefix, userDefinedHeaders, headers, headerPatterns); + Map standardHeaders = extractStandardHeaders(source); + this.copyHeaders(standardHeaders, headers, headerMatcher); + Map userDefinedHeaders = extractUserDefinedHeaders(source); + this.copyHeaders(userDefinedHeaders, headers, headerMatcher); return headers; } - private void copyHeaders(String prefix, Map source, Map target, List headerPatterns) { + private void copyHeaders(Map source, Map target, HeaderMatcher headerMatcher) { if (!CollectionUtils.isEmpty(source)) { for (Map.Entry entry : source.entrySet()) { try { - String headerName = this.addPrefixIfNecessary(prefix, entry.getKey()); - if (this.shouldMapHeader(headerName, headerPatterns)){ + String headerName = this.createTargetPropertyName(entry.getKey(), false); + if (this.shouldMapHeader(headerName, headerMatcher)) { target.put(headerName, entry.getValue()); } } @@ -228,39 +244,12 @@ public abstract class AbstractHeaderMapper implements RequestReplyHeaderMappe } } - private boolean shouldMapHeader(String headerName, List patterns) { + private boolean shouldMapHeader(String headerName, HeaderMatcher headerMatcher) { if (!StringUtils.hasText(headerName) - || ObjectUtils.containsElement(TRANSIENT_HEADER_NAMES, headerName)) { + || getTransientHeaderNames().contains(headerName)) { return false; } - if (patterns != null && patterns.size() > 0) { - for (String pattern : patterns) { - if (PatternMatchUtils.simpleMatch(pattern.toLowerCase(), headerName.toLowerCase())) { - if (logger.isDebugEnabled()) { - logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern)); - } - return true; - } - else if (STANDARD_REQUEST_HEADER_NAME_PATTERN.equals(pattern) - && this.containsElementIgnoreCase(this.getStandardRequestHeaderNames(), headerName)) { - if (logger.isDebugEnabled()) { - logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern)); - } - return true; - } - else if (STANDARD_REPLY_HEADER_NAME_PATTERN.equals(pattern) - && this.containsElementIgnoreCase(this.getStandardReplyHeaderNames(), headerName)) { - if (logger.isDebugEnabled()) { - logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern)); - } - return true; - } - } - } - if (logger.isDebugEnabled()) { - logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName)); - } - return false; + return headerMatcher.matchHeader(headerName); } @SuppressWarnings("unchecked") @@ -281,56 +270,216 @@ public abstract class AbstractHeaderMapper implements RequestReplyHeaderMappe } } - private boolean containsElementIgnoreCase(List headerNames, String name) { - for (String headerName : headerNames) { - if (headerName.equalsIgnoreCase(name)){ - return true; - } - } - return false; + /** + * Alter the specified {@code propertyName} if necessary. By default, the original + * {@code propertyName} is returned. + * @param propertyName the original name of the property. + * @param fromMessageHeaders specify if the property originates from a {@link MessageHeaders} + * instance (true) or from the type managed by this mapper (false). + * @return the property name for mapping. + */ + protected String createTargetPropertyName(String propertyName, boolean fromMessageHeaders) { + return propertyName; } /** - * Adds the prefix to the header name + * Return the transient header names. Transient headers are never mapped. + * @return the names of headers to be skipped from mapping. */ - private String addPrefixIfNecessary(String prefix, String propertyName) { - String headerName = propertyName; - if (StringUtils.hasText(prefix) && !headerName.startsWith(prefix) && - !headerName.equals(MessageHeaders.CONTENT_TYPE) && - (!JsonHeaders.HEADERS.contains(headerName) || !JsonHeaders.HEADERS.contains(JsonHeaders.PREFIX + headerName))) { - headerName = prefix + propertyName; - } - if (JsonHeaders.HEADERS.contains(JsonHeaders.PREFIX + headerName)) { - headerName = JsonHeaders.PREFIX + headerName; - } - return headerName; + protected Collection getTransientHeaderNames() { + return TRANSIENT_HEADER_NAMES; } /** - * @return The list of standard REQUEST headers. Implementation provided by a subclass + * Extract the standard headers from the specified source. + * @param source the source object to extract standard headers. + * @return the map of headers to be mapped. */ - protected List getStandardReplyHeaderNames(){ - return Collections.emptyList(); - } - - /** - * @return The PREFIX used by standard headers (if any) - */ - protected List getStandardRequestHeaderNames(){ - return Collections.emptyList(); - } - - /** - * @return The list of standard REPLY headers. Implementation provided by a subclass - */ - protected abstract String getStandardHeaderPrefix(); - protected abstract Map extractStandardHeaders(T source); + /** + * Extract the user-defined headers from the specified source. + * @param source the source object to extract user defined headers. + * @return the map of headers to be mapped. + */ protected abstract Map extractUserDefinedHeaders(T source); + /** + * Populate the specified standard headers to the specified source. + * @param headers the map of standard headers to be populated. + * @param target the target object to populate headers. + */ protected abstract void populateStandardHeaders(Map headers, T target); + /** + * Populate the specified user-defined headers to the specified source. + * @param headerName the user defined header name to be populated. + * @param headerValue the user defined header value to be populated. + * @param target the target object to populate headers. + */ protected abstract void populateUserDefinedHeader(String headerName, Object headerValue, T target); + /** + * Strategy interface to determine if a given header name matches. + * @since 4.1 + */ + public interface HeaderMatcher { + + /** + * Specify if the given {@code headerName} matches. + * @param headerName the header name to be matched. + * @return {@code true} if {@code headerName} matches to this {@link HeaderMatcher}. + */ + boolean matchHeader(String headerName); + + } + + /** + * A content-based {@link HeaderMatcher} that matches if the specified + * header is contained within a list of candidates. The case of the + * header does not matter. + * @since 4.1 + */ + protected static class ContentBasedHeaderMatcher implements HeaderMatcher { + + private static final Log logger = LogFactory.getLog(HeaderMatcher.class); + + private final boolean match; + + private final Collection content; + + public ContentBasedHeaderMatcher(boolean match, Collection content) { + this.match = match; + Assert.notNull(content, "Content must not be null"); + this.content = content; + } + + @Override + public boolean matchHeader(String headerName) { + boolean result = (this.match == containsIgnoreCase(headerName)); + if (result && logger.isDebugEnabled()) { + StringBuilder message = new StringBuilder("headerName=[{0}] WILL be mapped, "); + if (!this.match) { + message.append("not "); + } + message.append("found in {1}"); + logger.debug(MessageFormat.format(message.toString(), headerName, this.content)); + } + return result; + } + + private boolean containsIgnoreCase(String name) { + for (String headerName : this.content) { + if (headerName.equalsIgnoreCase(name)) { + return true; + } + } + return false; + } + + } + + /** + * A pattern-based {@link HeaderMatcher} that matches if the specified + * header match one of the specified simple patterns. + * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) + * @since 4.1 + */ + protected static class PatternBasedHeaderMatcher implements HeaderMatcher { + + private static final Log logger = LogFactory.getLog(HeaderMatcher.class); + + private final Collection patterns; + + public PatternBasedHeaderMatcher(Collection patterns) { + Assert.notNull(patterns, "Patters must no be null"); + Assert.notEmpty(patterns, "At least one pattern must be specified"); + this.patterns = patterns; + } + + @Override + public boolean matchHeader(String headerName) { + String header = headerName.toLowerCase(); + for (String pattern : this.patterns) { + if (PatternMatchUtils.simpleMatch(pattern.toLowerCase(), header)) { + if (logger.isDebugEnabled()) { + logger.debug(MessageFormat.format( + "headerName=[{0}] WILL be mapped, matched pattern={1}", headerName, pattern)); + } + return true; + } + } + return false; + } + + } + + /** + * A prefix-based {@link HeaderMatcher} that matches if the specified + * header starts with a configurable prefix. + * @since 4.1 + */ + protected static class PrefixBasedMatcher implements HeaderMatcher { + + private static final Log logger = LogFactory.getLog(HeaderMatcher.class); + + private final boolean match; + + private final String prefix; + + public PrefixBasedMatcher(boolean match, String prefix) { + this.match = match; + this.prefix = prefix; + } + + @Override + public boolean matchHeader(String headerName) { + boolean result = (this.match == headerName.startsWith(this.prefix)); + if (result && logger.isDebugEnabled()) { + StringBuilder message = new StringBuilder("headerName=[{0}] WILL be mapped, "); + if (!this.match) { + message.append("does not "); + } + message.append("start with [{1}]"); + logger.debug(MessageFormat.format(message.toString(), headerName, this.prefix)); + } + return result; + } + + } + + /** + * A composite {@link HeaderMatcher} that matches if one of provided + * {@link HeaderMatcher}s matches to the {@code headerName}. + * @since 4.1 + */ + protected static class CompositeHeaderMatcher implements HeaderMatcher { + + private static final Log logger = LogFactory.getLog(HeaderMatcher.class); + + private final Collection strategies; + + CompositeHeaderMatcher(Collection strategies) { + this.strategies = strategies; + } + + CompositeHeaderMatcher(HeaderMatcher... strategies) { + this(Arrays.asList(strategies)); + } + + @Override + public boolean matchHeader(String headerName) { + for (HeaderMatcher strategy : this.strategies) { + if (strategy.matchHeader(headerName)) { + return true; + } + } + if (logger.isDebugEnabled()) { + logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName)); + } + return false; + } + + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/mapping/RequestReplyHeaderMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/mapping/RequestReplyHeaderMapper.java index 0da2c0e60e..0f34c8bdf4 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/mapping/RequestReplyHeaderMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/mapping/RequestReplyHeaderMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,19 +23,40 @@ import org.springframework.messaging.MessageHeaders; * Request/Reply strategy interface for mapping {@link MessageHeaders} to and from other * types of objects. This would typically be used by adapters where the "other type" * has a concept of headers or properties (HTTP, JMS, AMQP, etc). - * - * @author Oleg Zhurakousky - * @since 2.1 * + * @param the type of the target object holding the headers + * @author Oleg Zhurakousky + * @author Stephane Nicoll + * @since 2.1 */ public interface RequestReplyHeaderMapper { + /** + * Map from the given {@link MessageHeaders} to the specified request target. + * @param headers the abstracted MessageHeaders + * @param target the native target request + */ void fromHeadersToRequest(MessageHeaders headers, T target); + /** + * Map from the given {@link MessageHeaders} to the specified reply target. + * @param headers the abstracted MessageHeaders + * @param target the native target reply + */ void fromHeadersToReply(MessageHeaders headers, T target); + /** + * Map from the given request object to abstracted {@link MessageHeaders}. + * @param source the native target request + * @return the abstracted MessageHeaders + */ Map toHeadersFromRequest(T source); + /** + * Map from the given reply object to abstracted {@link MessageHeaders}. + * @param source the native target reply + * @return the abstracted MessageHeaders + */ Map toHeadersFromReply(T source); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/mapping/HeaderMapperTests.java b/spring-integration-core/src/test/java/org/springframework/integration/mapping/HeaderMapperTests.java new file mode 100644 index 0000000000..117e5b5897 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/mapping/HeaderMapperTests.java @@ -0,0 +1,535 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.messaging.MessageHeaders; +import org.springframework.util.StringUtils; + +/** + * @author Stephane Nicoll + * @since 4.1 + */ +public class HeaderMapperTests { + + private final GenericTestHeaderMapper mapper = new GenericTestHeaderMapper(); + + @Test + public void toHeadersFromRequest() { + GenericTestProperties properties = createSimpleGenericTestProperties(); + + Map attributes = this.mapper.toHeadersFromRequest(properties); + assertEquals("appId", attributes.get(GenericTestHeaders.APP_ID)); + assertEquals("request-123", attributes.get(GenericTestHeaders.REQUEST_ONLY)); + assertFalse(attributes.containsKey(GenericTestHeaders.REPLY_ONLY)); + assertEquals("Wrong number of mapped header(s)", 2, attributes.size()); + } + + @Test + public void toHeadersFromRequestWithStar() { + this.mapper.setRequestHeaderNames("*"); + GenericTestProperties properties = createSimpleGenericTestProperties(); + + Map attributes = this.mapper.toHeadersFromRequest(properties); + assertEquals("appId", attributes.get(GenericTestHeaders.APP_ID)); + assertEquals("request-123", attributes.get(GenericTestHeaders.REQUEST_ONLY)); + assertEquals("reply-123", attributes.get(GenericTestHeaders.REPLY_ONLY)); + assertEquals("bar", attributes.get("foo")); + assertEquals("Wrong number of mapped header(s)", 4, attributes.size()); + } + + @Test + public void toHeadersFromRequestWithCustomPatterns() { + this.mapper.setRequestHeaderNames("foo*", "generic_reply*"); + GenericTestProperties properties = createSimpleGenericTestProperties(); + + Map attributes = this.mapper.toHeadersFromRequest(properties); + assertEquals(null, attributes.get(GenericTestHeaders.APP_ID)); + assertEquals(null, attributes.get(GenericTestHeaders.REQUEST_ONLY)); + assertEquals("reply-123", attributes.get(GenericTestHeaders.REPLY_ONLY)); + assertEquals("bar", attributes.get("foo")); + assertEquals("Wrong number of mapped header(s)", 2, attributes.size()); + } + + @Test + public void toHeadersFromRequestWithStandardRequestPattern() { + this.mapper.setRequestHeaderNames("foo*", GenericTestHeaderMapper.STANDARD_REQUEST_HEADER_NAME_PATTERN); + GenericTestProperties properties = createSimpleGenericTestProperties(); + properties.setUserDefinedHeader("foo2", "bar"); + properties.setUserDefinedHeader("something-else", "bar"); + + Map attributes = this.mapper.toHeadersFromRequest(properties); + assertEquals("appId", attributes.get(GenericTestHeaders.APP_ID)); + assertEquals("request-123", attributes.get(GenericTestHeaders.REQUEST_ONLY)); + assertFalse(attributes.containsKey(GenericTestHeaders.REPLY_ONLY)); + assertEquals("bar", attributes.get("foo")); + assertEquals("bar", attributes.get("foo2")); + assertEquals("Wrong number of mapped header(s)", 4, attributes.size()); + } + + @Test + public void toHeadersFromRequestWithOnlyStandardHeaders() { + this.mapper.setRequestHeaderNames(GenericTestHeaderMapper.STANDARD_REQUEST_HEADER_NAME_PATTERN); + GenericTestProperties properties = createSimpleGenericTestProperties(); + properties.setUserDefinedHeader("foo2", "bar"); + properties.setUserDefinedHeader("something-else", "bar"); + + Map attributes = this.mapper.toHeadersFromRequest(properties); + assertEquals("appId", attributes.get(GenericTestHeaders.APP_ID)); + assertEquals("request-123", attributes.get(GenericTestHeaders.REQUEST_ONLY)); + assertFalse(attributes.containsKey(GenericTestHeaders.REPLY_ONLY)); + assertEquals("Wrong number of mapped header(s)", 2, attributes.size()); + } + + @Test + public void toHeadersFromReply() { + GenericTestProperties properties = createSimpleGenericTestProperties(); + + Map attributes = this.mapper.toHeadersFromReply(properties); + assertEquals("appId", attributes.get(GenericTestHeaders.APP_ID)); + assertFalse(attributes.containsKey(GenericTestHeaders.REQUEST_ONLY)); + assertEquals("reply-123", attributes.get(GenericTestHeaders.REPLY_ONLY)); + assertEquals("Wrong number of mapped header(s)", 2, attributes.size()); + } + + @Test + public void toHeadersFromReplyWithStar() { + this.mapper.setReplyHeaderNames("*"); + GenericTestProperties properties = createSimpleGenericTestProperties(); + + Map attributes = this.mapper.toHeadersFromReply(properties); + assertEquals("appId", attributes.get(GenericTestHeaders.APP_ID)); + assertEquals("request-123", attributes.get(GenericTestHeaders.REQUEST_ONLY)); + assertEquals("reply-123", attributes.get(GenericTestHeaders.REPLY_ONLY)); + assertEquals("bar", attributes.get("foo")); + assertEquals("Wrong number of mapped header(s)", 4, attributes.size()); + } + + @Test + public void toHeadersFromReplyWithStandardReplyPattern() { + this.mapper.setReplyHeaderNames("foo*", GenericTestHeaderMapper.STANDARD_REPLY_HEADER_NAME_PATTERN); + GenericTestProperties properties = createSimpleGenericTestProperties(); + + properties.setUserDefinedHeader("foo2", "bar"); + properties.setUserDefinedHeader("something-else", "bar"); + + Map attributes = this.mapper.toHeadersFromReply(properties); + assertEquals("appId", attributes.get(GenericTestHeaders.APP_ID)); + assertFalse(attributes.containsKey(GenericTestHeaders.REQUEST_ONLY)); + assertEquals("reply-123", attributes.get(GenericTestHeaders.REPLY_ONLY)); + assertEquals("bar", attributes.get("foo")); + assertEquals("bar", attributes.get("foo2")); + assertEquals("Wrong number of mapped header(s)", 4, attributes.size()); + } + + @Test + public void toHeadersFromReplyWithOnlyStandardReplyHeaders() { + this.mapper.setReplyHeaderNames(GenericTestHeaderMapper.STANDARD_REPLY_HEADER_NAME_PATTERN); + GenericTestProperties properties = createSimpleGenericTestProperties(); + + properties.setUserDefinedHeader("foo2", "bar"); + properties.setUserDefinedHeader("something-else", "bar"); + + Map attributes = this.mapper.toHeadersFromReply(properties); + assertEquals("appId", attributes.get(GenericTestHeaders.APP_ID)); + assertFalse(attributes.containsKey(GenericTestHeaders.REQUEST_ONLY)); + assertEquals("reply-123", attributes.get(GenericTestHeaders.REPLY_ONLY)); + assertEquals("Wrong number of mapped header(s)", 2, attributes.size()); + } + + @Test + public void customTransientHeaderNames() { + GenericTestHeaderMapper customMapper = new GenericTestHeaderMapper() { + @Override + protected Collection getTransientHeaderNames() { + return Arrays.asList("foo", GenericTestHeaders.APP_ID); + } + }; + GenericTestProperties properties = createSimpleGenericTestProperties(); + + Map attributes = customMapper.toHeadersFromReply(properties); + // foo custom header and app Id not mapped + assertFalse(attributes.containsKey(GenericTestHeaders.APP_ID)); + assertFalse(attributes.containsKey("foo")); + assertEquals("Wrong number of mapped header(s)", 1, attributes.size()); + } + + private GenericTestProperties createSimpleGenericTestProperties() { + GenericTestProperties properties = new GenericTestProperties(); + properties.setAppId("appId"); + properties.setRequestOnly("request-123"); + properties.setReplyOnly("reply-123"); + + properties.setUserDefinedHeader("foo", "bar"); + return properties; + } + + @Test + public void fromHeadersToRequest() { + MessageHeaders messageHeaders = createSimpleMessageHeaders(); + 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(0, properties.getUserDefinedHeaders().size()); + } + + @Test + public void fromHeadersToRequestWithStar() { + this.mapper.setRequestHeaderNames("*"); + MessageHeaders messageHeaders = createSimpleMessageHeaders(); + 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()); + assertEquals("reply-456", properties.getReplyOnly()); + assertEquals("bar", properties.getUserDefinedHeaders().get("foo")); + assertEquals(1, properties.getUserDefinedHeaders().size()); + } + + @Test + public void fromHeadersToRequestWithStandardRequestPattern() { + this.mapper.setRequestHeaderNames("foo", GenericTestHeaderMapper.STANDARD_REQUEST_HEADER_NAME_PATTERN); + MessageHeaders messageHeaders = createSimpleMessageHeaders(); + 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("foo")); + assertEquals(1, properties.getUserDefinedHeaders().size()); + } + + @Test + public void fromHeadersToReply() { + MessageHeaders messageHeaders = createSimpleMessageHeaders(); + GenericTestProperties properties = new GenericTestProperties(); + this.mapper.fromHeadersToReply(messageHeaders, properties); + assertEquals("myAppId", properties.getAppId()); + assertNull(properties.getTransactionSize()); + assertEquals(true, properties.getRedelivered()); + assertNull(properties.getRequestOnly()); + assertEquals("reply-456", properties.getReplyOnly()); + assertEquals(0, properties.getUserDefinedHeaders().size()); + } + + @Test + public void fromHeadersToReplyWithStar() { + this.mapper.setReplyHeaderNames("*"); + MessageHeaders messageHeaders = createSimpleMessageHeaders(); + GenericTestProperties properties = new GenericTestProperties(); + this.mapper.fromHeadersToReply(messageHeaders, properties); + assertEquals("myAppId", properties.getAppId()); + assertNull(properties.getTransactionSize()); + assertEquals(true, properties.getRedelivered()); + assertEquals("request-456", properties.getRequestOnly()); + assertEquals("reply-456", properties.getReplyOnly()); + assertEquals("bar", properties.getUserDefinedHeaders().get("foo")); + assertEquals(1, properties.getUserDefinedHeaders().size()); + } + + + @Test + public void fromHeadersToReplyWithStandardReplyPattern() { + this.mapper.setReplyHeaderNames("foo", GenericTestHeaderMapper.STANDARD_REPLY_HEADER_NAME_PATTERN); + MessageHeaders messageHeaders = createSimpleMessageHeaders(); + GenericTestProperties properties = new GenericTestProperties(); + this.mapper.fromHeadersToReply(messageHeaders, properties); + assertEquals("myAppId", properties.getAppId()); + assertNull(properties.getTransactionSize()); + assertEquals(true, properties.getRedelivered()); + assertNull(properties.getRequestOnly()); + assertEquals("reply-456", properties.getReplyOnly()); + assertEquals("bar", properties.getUserDefinedHeaders().get("foo")); + assertEquals(1, properties.getUserDefinedHeaders().size()); + } + + public MessageHeaders createSimpleMessageHeaders() { + Map headers = new HashMap(); + 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", "bar"); + + return new MessageHeaders(headers); + } + + + @Test + public void prefixHeaderPatternMatching() { + PatternBasedHeaderMatcher strategy = + new PatternBasedHeaderMatcher(Collections.singleton("foo*")); + + assertMapping(strategy, "foo", true); + assertMapping(strategy, "foo123", true); + assertMapping(strategy, "FoO", true); + + assertMapping(strategy, "123foo", false); + assertMapping(strategy, "_foo", false); + } + + @Test + public void suffixHeaderPatternMatching() { + PatternBasedHeaderMatcher strategy = + 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 contentHeaderMatching() { + AbstractHeaderMapper.ContentBasedHeaderMatcher strategy = + new ContentBasedHeaderMatcher(true, Arrays.asList("foo", "bar")); + + assertMapping(strategy, "foo", true); + assertMapping(strategy, "bar", true); + assertMapping(strategy, "FOO", true); + assertMapping(strategy, "somethingElse", false); + } + + @Test + public void contentHeaderReverseMatching() { + ContentBasedHeaderMatcher strategy = + new ContentBasedHeaderMatcher(false, Arrays.asList("foo", "bar")); + + assertMapping(strategy, "foo", false); + assertMapping(strategy, "bar", false); + assertMapping(strategy, "somethingElse", true); + assertMapping(strategy, "anything", true); + } + + @Test + public void prefixHeaderMatching() { + PrefixBasedMatcher strategy = new PrefixBasedMatcher(true, "foo_"); + + assertMapping(strategy, "foo_", true); + assertMapping(strategy, "foo_ANYTHING", true); + assertMapping(strategy, "something_foo_", false); + assertMapping(strategy, "somethingElse", false); + } + + @Test + public void prefixHeaderReverseMatching() { + PrefixBasedMatcher strategy = new PrefixBasedMatcher(false, "foo_"); + + assertMapping(strategy, "foo_", false); + assertMapping(strategy, "foo_ANYTHING", false); + assertMapping(strategy, "something_foo_", true); + assertMapping(strategy, "somethingElse", true); + } + + @Test + public void compositeOneMatch() { + HeaderMatcher strategy = new CompositeHeaderMatcher( + new PrefixBasedMatcher(true, "foo_"), + new PrefixBasedMatcher(true, "bar_")); + + assertMapping(strategy, "foo_ANYTHING", true); + assertMapping(strategy, "bar_ANYTHING", true); + assertMapping(strategy, "somethingElse", false); + } + + + protected void assertMapping(HeaderMatcher strategy, String candidate, boolean match) { + assertEquals("Wrong mapping result for " + candidate + "", match, strategy.matchHeader(candidate)); + } + + + private static abstract class GenericTestHeaders { + + public static final String PREFIX = "generic_"; + + public static final String APP_ID = PREFIX + "appId"; + + public static final String TRANSACTION_SIZE = PREFIX + "transactionSize"; + + public static final String REDELIVERED = PREFIX + "redelivered"; + + public static final String REQUEST_ONLY = PREFIX + "requestOnly"; + + public static final String REPLY_ONLY = PREFIX + "replyOnly"; + + } + + private static class GenericTestHeaderMapper extends AbstractHeaderMapper { + + + private GenericTestHeaderMapper() { + super(GenericTestHeaders.PREFIX, + Arrays.asList(GenericTestHeaders.APP_ID, GenericTestHeaders.TRANSACTION_SIZE, + GenericTestHeaders.REDELIVERED, GenericTestHeaders.REQUEST_ONLY), + Arrays.asList(GenericTestHeaders.APP_ID, GenericTestHeaders.TRANSACTION_SIZE, + GenericTestHeaders.REDELIVERED, GenericTestHeaders.REPLY_ONLY)); + } + + @Override + protected Map extractStandardHeaders(GenericTestProperties source) { + Map result = new HashMap(); + if (StringUtils.hasText(source.getAppId())) { + result.put(GenericTestHeaders.APP_ID, source.getAppId()); + } + if (source.getTransactionSize() != null) { + result.put(GenericTestHeaders.TRANSACTION_SIZE, source.getTransactionSize()); + } + if (source.getRedelivered() != null) { + result.put(GenericTestHeaders.REDELIVERED, source.getRedelivered()); + } + if (StringUtils.hasText(source.getRequestOnly())) { + result.put(GenericTestHeaders.REQUEST_ONLY, source.getRequestOnly()); + } + if (StringUtils.hasText(source.getReplyOnly())) { + result.put(GenericTestHeaders.REPLY_ONLY, source.getReplyOnly()); + } + return result; + + } + + @Override + protected Map extractUserDefinedHeaders(GenericTestProperties source) { + return source.getUserDefinedHeaders(); + } + + @Override + protected void populateStandardHeaders(Map headers, GenericTestProperties target) { + String appId = getHeaderIfAvailable(headers, GenericTestHeaders.APP_ID, String.class); + if (StringUtils.hasText(appId)) { + target.setAppId(appId); + } + Integer transactionSize = getHeaderIfAvailable(headers, GenericTestHeaders.TRANSACTION_SIZE, Integer.class); + if (transactionSize != null) { + target.setTransactionSize(transactionSize); + } + Boolean redelivered = getHeaderIfAvailable(headers, GenericTestHeaders.REDELIVERED, Boolean.class); + if (redelivered != null) { + target.setRedelivered(redelivered); + } + String requestOnly = getHeaderIfAvailable(headers, GenericTestHeaders.REQUEST_ONLY, String.class); + if (StringUtils.hasText(requestOnly)) { + target.setRequestOnly(requestOnly); + } + String replyOnly = getHeaderIfAvailable(headers, GenericTestHeaders.REPLY_ONLY, String.class); + if (StringUtils.hasText(replyOnly)) { + target.setReplyOnly(replyOnly); + } + + } + + @Override + protected void populateUserDefinedHeader(String headerName, Object headerValue, GenericTestProperties target) { + target.setUserDefinedHeader(headerName, headerValue); + } + + } + + private static class GenericTestProperties { + + private String appId; + + private Integer transactionSize; + + private Boolean redelivered; + + private String requestOnly; + + private String replyOnly; + + private final Map userDefinedHeaders = new HashMap(); + + private GenericTestProperties() { + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public Integer getTransactionSize() { + return transactionSize; + } + + public void setTransactionSize(Integer transactionSize) { + this.transactionSize = transactionSize; + } + + public Boolean getRedelivered() { + return redelivered; + } + + public void setRedelivered(boolean redelivered) { + this.redelivered = redelivered; + } + + public String getRequestOnly() { + return requestOnly; + } + + public void setRequestOnly(String requestOnly) { + this.requestOnly = requestOnly; + } + + public String getReplyOnly() { + return replyOnly; + } + + public void setReplyOnly(String replyOnly) { + this.replyOnly = replyOnly; + } + + public Map getUserDefinedHeaders() { + return userDefinedHeaders; + } + + public void setUserDefinedHeader(String name, Object value) { + this.userDefinedHeaders.put(name, value); + } + + } + +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java index fa03331c9f..3ab8c07141 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java @@ -134,6 +134,7 @@ public interface RemoteFileOperations { * * @param callback the ClientCallback. * @param The type returned by {@link ClientCallback#doWithClient(Object)}. + * @param The type of the underlying client object. * @return The result of the callback method. * @since 4.1 */ diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/DefaultSoapHeaderMapper.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/DefaultSoapHeaderMapper.java index 934935ece1..c7e3af944b 100644 --- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/DefaultSoapHeaderMapper.java +++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/DefaultSoapHeaderMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,7 @@ import org.springframework.xml.namespace.QNameUtils; * * @author Mark Fisher * @author Oleg Zhurakousky + * @author Stephane Nicoll * @since 2.0 */ public class DefaultSoapHeaderMapper extends AbstractHeaderMapper implements SoapHeaderMapper { @@ -52,9 +53,12 @@ public class DefaultSoapHeaderMapper extends AbstractHeaderMapper i static { STANDARD_HEADER_NAMES.add(WebServiceHeaders.SOAP_ACTION); - } - + + public DefaultSoapHeaderMapper() { + super(WebServiceHeaders.PREFIX, STANDARD_HEADER_NAMES, Collections.emptyList()); + } + @Override protected Map extractStandardHeaders(SoapMessage source) { return Collections.emptyMap(); @@ -106,14 +110,5 @@ public class DefaultSoapHeaderMapper extends AbstractHeaderMapper i soapHeader.addAttribute(qname, (String) headerValue); } } - - @Override - protected List getStandardRequestHeaderNames() { - return STANDARD_HEADER_NAMES; - } - @Override - protected String getStandardHeaderPrefix() { - return WebServiceHeaders.PREFIX; - } } diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java index 53562fe826..52f75f598b 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.integration.ws.config; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Properties; @@ -30,6 +29,7 @@ import org.mockito.Mockito; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.mapping.AbstractHeaderMapper; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; @@ -52,10 +52,9 @@ import org.springframework.ws.soap.SoapMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.hamcrest.CoreMatchers.is; - +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; - +import static org.hamcrest.CoreMatchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -64,6 +63,7 @@ import static org.mockito.Mockito.when; * @author Oleg Zhurakousky * @author Mark Fisher * @author Gunnar Hillert + * @author Stephane Nicoll */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @@ -144,14 +144,16 @@ public class WebServiceInboundGatewayParserTests { assertThat( (MessageChannel) accessor.getPropertyValue("errorChannel"), is(customErrorChannel)); - @SuppressWarnings("unchecked") - List requestHeaders = TestUtils.getPropertyValue(marshallingGateway, "headerMapper.requestHeaderNames", List.class); - @SuppressWarnings("unchecked") - List replyHeaders = TestUtils.getPropertyValue(marshallingGateway, "headerMapper.replyHeaderNames", List.class); - assertEquals(1, requestHeaders.size()); - assertEquals(1, replyHeaders.size()); - assertTrue(requestHeaders.contains("testRequest")); - assertTrue(replyHeaders.contains("testReply")); + + AbstractHeaderMapper.HeaderMatcher requestHeaderMatcher = TestUtils.getPropertyValue(marshallingGateway, + "headerMapper.requestHeaderMatcher", AbstractHeaderMapper.HeaderMatcher.class); + assertTrue(requestHeaderMatcher.matchHeader("testRequest")); + assertFalse(requestHeaderMatcher.matchHeader("testReply")); + + AbstractHeaderMapper.HeaderMatcher replyHeaderMatcher = TestUtils.getPropertyValue(marshallingGateway, + "headerMapper.replyHeaderMatcher", AbstractHeaderMapper.HeaderMatcher.class); + assertFalse(replyHeaderMatcher.matchHeader("testRequest")); + assertTrue(replyHeaderMatcher.matchHeader("testReply")); } @Test diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java index 883e1e9476..5edf01d9d9 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ package org.springframework.integration.ws.config; import static org.junit.Assert.*; import static org.mockito.Mockito.*; -import java.util.List; import org.junit.Assert; import org.junit.Test; @@ -32,6 +31,7 @@ import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.PollingConsumer; import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.mapping.AbstractHeaderMapper; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.ws.MarshallingWebServiceOutboundGateway; import org.springframework.integration.ws.SimpleWebServiceOutboundGateway; @@ -75,14 +75,15 @@ public class WebServiceOutboundGatewayParserTests { assertEquals(expected, accessor.getPropertyValue("outputChannel")); Assert.assertEquals(Boolean.FALSE, accessor.getPropertyValue("requiresReply")); - @SuppressWarnings("unchecked") - List requestHeaders = TestUtils.getPropertyValue(endpoint, "handler.headerMapper.requestHeaderNames", List.class); - @SuppressWarnings("unchecked") - List replyHeaders = TestUtils.getPropertyValue(endpoint, "handler.headerMapper.replyHeaderNames", List.class); - assertEquals(1, requestHeaders.size()); - assertEquals(1, replyHeaders.size()); - assertTrue(requestHeaders.contains("testRequest")); - assertTrue(replyHeaders.contains("testReply")); + AbstractHeaderMapper.HeaderMatcher requestHeaderMatcher = TestUtils.getPropertyValue(endpoint, + "handler.headerMapper.requestHeaderMatcher", AbstractHeaderMapper.HeaderMatcher.class); + assertTrue(requestHeaderMatcher.matchHeader("testRequest")); + assertFalse(requestHeaderMatcher.matchHeader("testReply")); + + AbstractHeaderMapper.HeaderMatcher replyHeaderMatcher = TestUtils.getPropertyValue(endpoint, + "handler.headerMapper.replyHeaderMatcher", AbstractHeaderMapper.HeaderMatcher.class); + assertFalse(replyHeaderMatcher.matchHeader("testRequest")); + assertTrue(replyHeaderMatcher.matchHeader("testReply")); Long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class); assertEquals(Long.valueOf(777), sendTimeout); diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayWithHeaderMapperTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayWithHeaderMapperTests.java index 4f34594f71..2cfd5e5eb1 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayWithHeaderMapperTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayWithHeaderMapperTests.java @@ -17,6 +17,7 @@ package org.springframework.integration.ws.config; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -24,7 +25,6 @@ import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; -import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -39,6 +39,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.mapping.AbstractHeaderMapper; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.integration.channel.QueueChannel; @@ -90,14 +91,23 @@ public class WebServiceOutboundGatewayWithHeaderMapperTests { DefaultSoapHeaderMapper headerMapper = TestUtils.getPropertyValue(gateway, "headerMapper", DefaultSoapHeaderMapper.class); assertNotNull(headerMapper); - List requestHeaderNames = TestUtils.getPropertyValue(headerMapper, "requestHeaderNames", List.class); - assertEquals(2, requestHeaderNames.size()); - assertEquals("foo*", requestHeaderNames.get(0)); - assertEquals("*baz*", requestHeaderNames.get(1)); + AbstractHeaderMapper.HeaderMatcher requestHeaderMatcher = TestUtils.getPropertyValue(headerMapper, + "requestHeaderMatcher", AbstractHeaderMapper.HeaderMatcher.class); + assertTrue(requestHeaderMatcher.matchHeader("foo")); + assertTrue(requestHeaderMatcher.matchHeader("foo123")); + assertTrue(requestHeaderMatcher.matchHeader("baz")); + assertTrue(requestHeaderMatcher.matchHeader("123baz123")); + assertFalse(requestHeaderMatcher.matchHeader("bar")); + assertFalse(requestHeaderMatcher.matchHeader("bar123")); - List responseHeaderNames = TestUtils.getPropertyValue(headerMapper, "replyHeaderNames", List.class); - assertEquals(1, responseHeaderNames.size()); - assertEquals("bar*", responseHeaderNames.get(0)); + AbstractHeaderMapper.HeaderMatcher replyHeaderMatcher = TestUtils.getPropertyValue(headerMapper, + "replyHeaderMatcher", AbstractHeaderMapper.HeaderMatcher.class); + assertFalse(replyHeaderMatcher.matchHeader("foo")); + assertFalse(replyHeaderMatcher.matchHeader("foo123")); + assertFalse(replyHeaderMatcher.matchHeader("baz")); + assertFalse(replyHeaderMatcher.matchHeader("123baz123")); + assertTrue(replyHeaderMatcher.matchHeader("bar")); + assertTrue(replyHeaderMatcher.matchHeader("bar123")); } @Test diff --git a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/support/DefaultXmppHeaderMapper.java b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/support/DefaultXmppHeaderMapper.java index 19e460ac58..fa34d6de19 100644 --- a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/support/DefaultXmppHeaderMapper.java +++ b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/support/DefaultXmppHeaderMapper.java @@ -34,6 +34,7 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @author Oleg Zhurakousky * @author Florian Schmaus + * @author Stephane Nicoll * * @since 2.1 */ @@ -49,6 +50,10 @@ public class DefaultXmppHeaderMapper extends AbstractHeaderMapper imple STANDARD_HEADER_NAMES.add(XmppHeaders.TYPE); } + public DefaultXmppHeaderMapper() { + super(XmppHeaders.PREFIX, STANDARD_HEADER_NAMES, STANDARD_HEADER_NAMES); + } + @Override protected Map extractStandardHeaders(Message source) { Map headers = new HashMap(); @@ -128,18 +133,4 @@ public class DefaultXmppHeaderMapper extends AbstractHeaderMapper imple JivePropertiesManager.addProperty(target, headerName, headerValue); } - @Override - protected List getStandardReplyHeaderNames() { - return STANDARD_HEADER_NAMES; - } - - @Override - protected List getStandardRequestHeaderNames() { - return STANDARD_HEADER_NAMES; - } - @Override - protected String getStandardHeaderPrefix() { - return XmppHeaders.PREFIX; - } - } diff --git a/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-4.1.xsd b/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-4.1.xsd index bb20ec02c4..e51e634ee8 100644 --- a/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-4.1.xsd +++ b/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-4.1.xsd @@ -259,7 +259,7 @@ - The conversation thread id used to corelate XMPP packets as + The conversation thread id used to correlate XMPP packets as belonging to a particular conversation diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java index 50ae3d53a4..8f9798a533 100644 --- a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java +++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java @@ -18,11 +18,10 @@ package org.springframework.integration.xmpp.config; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import java.util.List; - import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smackx.jiveproperties.JivePropertiesManager; import org.junit.Test; @@ -32,6 +31,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.integration.mapping.AbstractHeaderMapper; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.integration.channel.QueueChannel; @@ -96,10 +96,15 @@ public class ChatMessageOutboundChannelAdapterParserTests { Object eventConsumer = context.getBean("outboundEventAdapter"); DefaultXmppHeaderMapper headerMapper = TestUtils.getPropertyValue(eventConsumer, "handler.headerMapper", DefaultXmppHeaderMapper.class); - List requestHeaderNames = TestUtils.getPropertyValue(headerMapper, "requestHeaderNames", List.class); - assertEquals(2, requestHeaderNames.size()); - assertEquals("foo*", requestHeaderNames.get(0)); - assertEquals("bar*", requestHeaderNames.get(1)); + + AbstractHeaderMapper.HeaderMatcher requestHeaderMatcher = TestUtils.getPropertyValue(headerMapper, + "requestHeaderMatcher", AbstractHeaderMapper.HeaderMatcher.class); + assertTrue(requestHeaderMatcher.matchHeader("foo")); + assertTrue(requestHeaderMatcher.matchHeader("foo123")); + assertTrue(requestHeaderMatcher.matchHeader("bar")); + assertTrue(requestHeaderMatcher.matchHeader("bar123")); + assertFalse(requestHeaderMatcher.matchHeader("biz")); + assertFalse(requestHeaderMatcher.matchHeader("else")); assertTrue(eventConsumer instanceof EventDrivenConsumer); } diff --git a/src/reference/docbook/amqp.xml b/src/reference/docbook/amqp.xml index fcac39da59..f63c71d7b0 100644 --- a/src/reference/docbook/amqp.xml +++ b/src/reference/docbook/amqp.xml @@ -698,7 +698,7 @@ public Object handle(@Payload String payload, @Header(AmqpHeaders.CHANNEL) Chann "pollable" option for a publish-subscribe-channel; it must be message-driven. -

+
AMQP Message Headers The Spring Integration AMPQ Adapters will map standard AMQP properties @@ -724,6 +724,15 @@ public Object handle(@Payload String payload, @Header(AmqpHeaders.CHANNEL) Chann if you need to copy all user-defined headers simply use the wild-card character '*'. + + Starting with version 4.1, the AbstractHeaderMapper + (a DefaultAmqpHeaderMapper superclass) allows the NON_STANDARD_HEADERS + token to be configured for the requestHeaderNames and/or replyHeaderNames + properties (in addition to existing STANDARD_REQUEST_HEADERS and + STANDARD_REPLY_HEADERS) to map all user-defined headers. Note, it is recommended to use the + combination like this STANDARD_REPLY_HEADERS, NON_STANDARD_HEADERS instead of + generic *, to avoid mapping of request headers to the reply. + Class AmqpHeaders identifies the default headers that will be used by the diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index f1483bb1ef..e15ec49615 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -146,5 +146,13 @@ See for more information.
+
+ AbstractHeaderMapper: NON_STANDARD_HEADERS token + + The AbstractHeaderMapper implementations now provides the additional + NON_STANDARD_HEADERS token to map any user-defined headers, which aren't mapped by default. + See for more information. + +