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
This commit is contained in:
Stéphane Nicoll
2014-08-19 12:01:10 +03:00
committed by Artem Bilan
parent 84421fd91c
commit 463c185b38
16 changed files with 988 additions and 263 deletions

View File

@@ -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<MessageProperties> implements AmqpHeaderMapper {
@@ -80,6 +81,10 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_TO_STACK);
}
public DefaultAmqpHeaderMapper() {
super(AmqpHeaders.PREFIX, STANDARD_HEADER_NAMES, STANDARD_HEADER_NAMES);
}
/**
* Extract "standard" headers from an AMQP MessageProperties instance.
*/
@@ -331,24 +336,6 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
}
}
@Override
protected List<String> getStandardRequestHeaderNames() {
return STANDARD_HEADER_NAMES;
}
@Override
protected List<String> 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

View File

@@ -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.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -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.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -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.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -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.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -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<String, Object> headerMap = new HashMap<String, Object>();
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<String, Object> headerMap = new HashMap<String, Object>();
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

View File

@@ -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<T> implements RequestReplyHeaderMapper<T> {
/**
* 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<String> 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<String> requestHeaderNames;
private volatile List<String> requestHeaderNames = new ArrayList<String>();
private final Collection<String> replyHeaderNames;
private volatile List<String> replyHeaderNames = new ArrayList<String>();
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<String> requestHeaderNames, Collection<String> 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.
* <p>
* 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}.
* <p>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.
* <p>
* 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}.
* <p>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.
* <p>
* 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<String> 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}.
* <p>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<String> patterns) {
Collection<HeaderMatcher> matchers = new ArrayList<HeaderMatcher>();
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<String, Object> 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<String, Object> toHeadersFromReply(T source) {
return this.toHeaders(source, this.replyHeaderNames);
this.fromHeaders(headers, target, this.requestHeaderMatcher);
}
private void fromHeaders(MessageHeaders headers, T target, List<String> headerPatterns){
@Override
public void fromHeadersToReply(MessageHeaders headers, T target) {
this.fromHeaders(headers, target, this.replyHeaderMatcher);
}
@Override
public Map<String, Object> toHeadersFromRequest(T source) {
return this.toHeaders(source, this.requestHeaderMatcher);
}
@Override
public Map<String, Object> toHeadersFromReply(T source) {
return this.toHeaders(source, this.replyHeaderMatcher);
}
private void fromHeaders(MessageHeaders headers, T target, HeaderMatcher headerMatcher) {
try {
Map<String, Object> subset = new HashMap<String, Object>();
for (String headerName : headers.keySet()) {
if (this.shouldMapHeader(headerName, headerPatterns)){
subset.put(headerName, headers.get(headerName));
for (Map.Entry<String, Object> 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<T> 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<T> 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<String, Object> toHeaders(T source, List<String> headerPatterns) {
private Map<String, Object> toHeaders(T source, HeaderMatcher headerMatcher) {
Map<String, Object> headers = new HashMap<String, Object>();
Map<String, Object> standardHeaders = this.extractStandardHeaders(source);
this.copyHeaders(this.standardHeaderPrefix, standardHeaders, headers, headerPatterns);
Map<String, Object> userDefinedHeaders = this.extractUserDefinedHeaders(source);
this.copyHeaders(this.userDefinedHeaderPrefix, userDefinedHeaders, headers, headerPatterns);
Map<String, Object> standardHeaders = extractStandardHeaders(source);
this.copyHeaders(standardHeaders, headers, headerMatcher);
Map<String, Object> userDefinedHeaders = extractUserDefinedHeaders(source);
this.copyHeaders(userDefinedHeaders, headers, headerMatcher);
return headers;
}
private <V> void copyHeaders(String prefix, Map<String, Object> source, Map<String, Object> target, List<String> headerPatterns) {
private <V> void copyHeaders(Map<String, Object> source, Map<String, Object> target, HeaderMatcher headerMatcher) {
if (!CollectionUtils.isEmpty(source)) {
for (Map.Entry<String, Object> 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<T> implements RequestReplyHeaderMappe
}
}
private boolean shouldMapHeader(String headerName, List<String> 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<T> implements RequestReplyHeaderMappe
}
}
private boolean containsElementIgnoreCase(List<String> 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<String> 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<String> getStandardReplyHeaderNames(){
return Collections.emptyList();
}
/**
* @return The PREFIX used by standard headers (if any)
*/
protected List<String> getStandardRequestHeaderNames(){
return Collections.emptyList();
}
/**
* @return The list of standard REPLY headers. Implementation provided by a subclass
*/
protected abstract String getStandardHeaderPrefix();
protected abstract Map<String, Object> 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<String, Object> 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<String, Object> 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<String> content;
public ContentBasedHeaderMatcher(boolean match, Collection<String> 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<String> patterns;
public PatternBasedHeaderMatcher(Collection<String> 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<HeaderMatcher> strategies;
CompositeHeaderMatcher(Collection<HeaderMatcher> 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;
}
}
}

View File

@@ -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 <T> the type of the target object holding the headers
* @author Oleg Zhurakousky
* @author Stephane Nicoll
* @since 2.1
*/
public interface RequestReplyHeaderMapper<T> {
/**
* 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<String, Object> toHeadersFromRequest(T source);
/**
* Map from the given reply object to abstracted {@link MessageHeaders}.
* @param source the native target reply
* @return the abstracted MessageHeaders
*/
Map<String, Object> toHeadersFromReply(T source);
}

View File

@@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String> getTransientHeaderNames() {
return Arrays.asList("foo", GenericTestHeaders.APP_ID);
}
};
GenericTestProperties properties = createSimpleGenericTestProperties();
Map<String, Object> 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<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", "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<GenericTestProperties> {
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<String, Object> extractStandardHeaders(GenericTestProperties source) {
Map<String, Object> result = new HashMap<String, Object>();
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<String, Object> extractUserDefinedHeaders(GenericTestProperties source) {
return source.getUserDefinedHeaders();
}
@Override
protected void populateStandardHeaders(Map<String, Object> 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<String, Object> userDefinedHeaders = new HashMap<String, Object>();
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<String, Object> getUserDefinedHeaders() {
return userDefinedHeaders;
}
public void setUserDefinedHeader(String name, Object value) {
this.userDefinedHeaders.put(name, value);
}
}
}

View File

@@ -134,6 +134,7 @@ public interface RemoteFileOperations<F> {
*
* @param callback the ClientCallback.
* @param <T> The type returned by {@link ClientCallback#doWithClient(Object)}.
* @param <C> The type of the underlying client object.
* @return The result of the callback method.
* @since 4.1
*/

View File

@@ -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<SoapMessage> implements SoapHeaderMapper {
@@ -52,9 +53,12 @@ public class DefaultSoapHeaderMapper extends AbstractHeaderMapper<SoapMessage> i
static {
STANDARD_HEADER_NAMES.add(WebServiceHeaders.SOAP_ACTION);
}
public DefaultSoapHeaderMapper() {
super(WebServiceHeaders.PREFIX, STANDARD_HEADER_NAMES, Collections.<String>emptyList());
}
@Override
protected Map<String, Object> extractStandardHeaders(SoapMessage source) {
return Collections.emptyMap();
@@ -106,14 +110,5 @@ public class DefaultSoapHeaderMapper extends AbstractHeaderMapper<SoapMessage> i
soapHeader.addAttribute(qname, (String) headerValue);
}
}
@Override
protected List<String> getStandardRequestHeaderNames() {
return STANDARD_HEADER_NAMES;
}
@Override
protected String getStandardHeaderPrefix() {
return WebServiceHeaders.PREFIX;
}
}

View File

@@ -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<String> requestHeaders = TestUtils.getPropertyValue(marshallingGateway, "headerMapper.requestHeaderNames", List.class);
@SuppressWarnings("unchecked")
List<String> 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

View File

@@ -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<String> requestHeaders = TestUtils.getPropertyValue(endpoint, "handler.headerMapper.requestHeaderNames", List.class);
@SuppressWarnings("unchecked")
List<String> 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);

View File

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

View File

@@ -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<Message> imple
STANDARD_HEADER_NAMES.add(XmppHeaders.TYPE);
}
public DefaultXmppHeaderMapper() {
super(XmppHeaders.PREFIX, STANDARD_HEADER_NAMES, STANDARD_HEADER_NAMES);
}
@Override
protected Map<String, Object> extractStandardHeaders(Message source) {
Map<String, Object> headers = new HashMap<String, Object>();
@@ -128,18 +133,4 @@ public class DefaultXmppHeaderMapper extends AbstractHeaderMapper<Message> imple
JivePropertiesManager.addProperty(target, headerName, headerValue);
}
@Override
protected List<String> getStandardReplyHeaderNames() {
return STANDARD_HEADER_NAMES;
}
@Override
protected List<String> getStandardRequestHeaderNames() {
return STANDARD_HEADER_NAMES;
}
@Override
protected String getStandardHeaderPrefix() {
return XmppHeaders.PREFIX;
}
}

View File

@@ -259,7 +259,7 @@
<xsd:element name="chat-thread-id" type="headerType">
<xsd:annotation>
<xsd:documentation>
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
</xsd:documentation>
</xsd:annotation>

View File

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

View File

@@ -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.
</para>
</section>
<section>
<section id="amqp-message-headers">
<title>AMQP Message Headers</title>
<para>
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 '*'.
</tip>
<para>
Starting with <emphasis>version 4.1</emphasis>, the <classname>AbstractHeaderMapper</classname>
(a <classname>DefaultAmqpHeaderMapper</classname> superclass) allows the <code>NON_STANDARD_HEADERS</code>
token to be configured for the <emphasis>requestHeaderNames</emphasis> and/or <emphasis>replyHeaderNames</emphasis>
properties (in addition to existing <code>STANDARD_REQUEST_HEADERS</code> and <code>
STANDARD_REPLY_HEADERS</code>) to map all user-defined headers. Note, it is recommended to use the
combination like this <code>STANDARD_REPLY_HEADERS, NON_STANDARD_HEADERS</code> instead of
generic <code>*</code>, to avoid mapping of <emphasis>request</emphasis> headers to the reply.
</para>
<para>
Class <classname><ulink url="http://static.springsource.org/spring-integration/api/org/springframework/integration/amqp/AmqpHeaders.html">AmqpHeaders</ulink></classname>
identifies the default headers that will be used by the

View File

@@ -146,5 +146,13 @@
See <xref linkend="recipient-list-router-management"/> for more information.
</para>
</section>
<section id="4.1-AbstractHeaderMapper-changes">
<title>AbstractHeaderMapper: NON_STANDARD_HEADERS token</title>
<para>
The <classname>AbstractHeaderMapper</classname> implementations now provides the additional
<code>NON_STANDARD_HEADERS</code> token to map any user-defined headers, which aren't mapped by default.
See <xref linkend="amqp-message-headers"/> for more information.
</para>
</section>
</section>
</chapter>