HeaderMapper refactoring
INT-2083 added outbound namespace support for XMPP header mapper INT-2083 added inbound namespace support for XMPP header mapper INT-2083 polished XMPP inbound/outbound header mappings, added namespace support for AMQP inbound adapter/gateway header mappings INT-2083 added support and tests for AMQP outbound gateways and adapters INT-2083 polishing and adding more tests for AMQP support for header mappings INT-2083 polishing AMQP and XMPP header mappings, generalized headerMapper configuration in IntegrationNamespaceUtils.configureHeaderMapper(..) method INT-2803 added headermapping support to WS outbound gateways INT-2803 polishing INT-2083 refactored SimpleWebServiceOutboundGateway, added full request/reply test INT-2083 polished MarshallingWebServiceOutboundGateway to add marshalling callback handlers, added test for marshalling call INT-2083 polishing based on PR comments INT-2083 interim commit INT-2083 added RequestReplyHeaderMapper startegy and migrated AMQP Header Mapper to use it INT-2083 migrated WS and XMPP to use a new RequestReplyHeaderMapper strategy INT-2083 polishing with PR comments INT-2083 polishing removed introspection method, simplified things INT-2083 polishing based on recent PR comments INT-2083 interim commit INT-2083 polishing WS module INT-2083 refactored to make sure that Soap action header is set within HeaderMapper INT-2083 polishing ensured the Soap header is set to default value if not provided INT-2083 polishing, putting tests back
This commit is contained in:
@@ -26,7 +26,6 @@ import org.springframework.core.Conventions;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
@@ -220,4 +219,37 @@ public abstract class IntegrationNamespaceUtils {
|
||||
return innerComponentDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to configure HeaderMapper for Inbound and Outbound channel adapters/gateway
|
||||
*/
|
||||
public static void configureHeaderMapper(Element element, BeanDefinitionBuilder rootBuilder, ParserContext parserContext, Class<?> headerMapperClass, String replyHeaderValue){
|
||||
String defaultMappedReplyHeadersAttributeName = "mapped-reply-headers";
|
||||
if (!StringUtils.hasText(replyHeaderValue)){
|
||||
replyHeaderValue = defaultMappedReplyHeadersAttributeName;
|
||||
}
|
||||
boolean hasHeaderMapper = element.hasAttribute("header-mapper");
|
||||
boolean hasMappedRequestHeaders = element.hasAttribute("mapped-request-headers");
|
||||
boolean hasMappedReplyHeaders = element.hasAttribute(replyHeaderValue);
|
||||
|
||||
if (hasHeaderMapper && (hasMappedRequestHeaders || hasMappedReplyHeaders)){
|
||||
parserContext.getReaderContext().error("The 'header-mapper' attribute is mutually exclusive with" +
|
||||
" 'mapped-request-headers' or 'mapped-reply-headers'. " +
|
||||
"You can only use one or the others", element);
|
||||
}
|
||||
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(rootBuilder, element, "header-mapper");
|
||||
|
||||
if (hasMappedRequestHeaders || hasMappedReplyHeaders){
|
||||
BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(headerMapperClass);
|
||||
|
||||
if (hasMappedRequestHeaders) {
|
||||
headerMapperBuilder.addPropertyValue("requestHeaderNames", element.getAttribute("mapped-request-headers"));
|
||||
}
|
||||
if (hasMappedReplyHeaders) {
|
||||
headerMapperBuilder.addPropertyValue("replyHeaderNames", element.getAttribute(replyHeaderValue));
|
||||
}
|
||||
|
||||
rootBuilder.addPropertyValue("headerMapper", headerMapperBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright 2002-2011 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 java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.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.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 2.1
|
||||
*/
|
||||
public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMapper<T> {
|
||||
|
||||
public static final String STANDARD_REQUEST_HEADER_NAME_PATTERN = "STANDARD_REQUEST_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
|
||||
};
|
||||
|
||||
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
private final String standardHeaderPrefix;
|
||||
|
||||
private volatile String userDefinedHeaderPrefix = "";
|
||||
|
||||
private volatile List<String> requestHeaderNames = new ArrayList<String>();
|
||||
|
||||
private volatile List<String> replyHeaderNames = new ArrayList<String>();
|
||||
|
||||
protected AbstractHeaderMapper() {
|
||||
this.standardHeaderPrefix = this.getStandardHeaderPrefix();
|
||||
this.requestHeaderNames.addAll(this.getStandardRequestHeaderNames());
|
||||
this.replyHeaderNames.addAll(this.getStandardReplyHeaderNames());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 specified by {@link #setInboundPrefix(String)}.
|
||||
*/
|
||||
public void setRequestHeaderNames(String[] requestHeaderNames) {
|
||||
Assert.notNull(requestHeaderNames, "'requestHeaderNames' must not be null");
|
||||
this.requestHeaderNames = 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 #setOutboundPrefix(String)}.
|
||||
*/
|
||||
public void setReplyHeaderNames(String[] replyHeaderNames) {
|
||||
Assert.notNull(replyHeaderNames, "'replyHeaderNames' must not be null");
|
||||
this.replyHeaderNames = 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).
|
||||
*/
|
||||
public void setUserDefinedHeaderPrefix(String userDefinedHeaderPrefix) {
|
||||
this.userDefinedHeaderPrefix = (userDefinedHeaderPrefix != null) ? userDefinedHeaderPrefix : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps headers from a Spring Integration MessageHeaders instance to the target instance
|
||||
* matching on the set of REQUEST headers (if different).
|
||||
*/
|
||||
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).
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public Map<String, Object> toHeadersFromReply(T source) {
|
||||
return this.toHeaders(source, this.replyHeaderNames);
|
||||
}
|
||||
|
||||
private void fromHeaders(MessageHeaders headers, T target, List<String> headerPatterns){
|
||||
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));
|
||||
}
|
||||
}
|
||||
this.populateStandardHeaders(subset, target);
|
||||
this.populateUserDefinedHeaders(subset, target);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("error occurred while mapping from MessageHeaders", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void populateUserDefinedHeaders(Map<String, Object> headers, T target) {
|
||||
for (String headerName : headers.keySet()) {
|
||||
Object value = headers.get(headerName);
|
||||
if (value != null) {
|
||||
try {
|
||||
String key = this.addPrefixIfNecessary(this.userDefinedHeaderPrefix, headerName);
|
||||
this.populateUserDefinedHeader(key, value, target);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("failed to map from Message header '" + headerName + "' to target", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps headers from a source instance to the MessageHeaders of a
|
||||
* Spring Integration Message.
|
||||
*/
|
||||
private Map<String, Object> toHeaders(T source, List<String> headerPatterns) {
|
||||
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);
|
||||
return headers;
|
||||
}
|
||||
|
||||
private <V> void copyHeaders(String prefix, Map<String, Object> source, Map<String, Object> target, List<String> headerPatterns) {
|
||||
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)){
|
||||
target.put(headerName, entry.getValue());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("error occurred while mapping header '"
|
||||
+ entry.getKey() + "' to Message header", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldMapHeader(String headerName, List<String> patterns) {
|
||||
if (!StringUtils.hasText(headerName)
|
||||
|| ObjectUtils.containsElement(TRANSIENT_HEADER_NAMES, 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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <V> V getHeaderIfAvailable(Map<String, Object> headers, String name, Class<V> type) {
|
||||
Object value = headers.get(name);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (!type.isAssignableFrom(value.getClass())) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("skipping header '" + name + "' since it is not of expected type [" + type + "]");
|
||||
}
|
||||
}
|
||||
return (V) value;
|
||||
}
|
||||
|
||||
private boolean containsElementIgnoreCase(List<String> headerNames, String name) {
|
||||
for (String headerName : headerNames) {
|
||||
if (headerName.equalsIgnoreCase(name)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the prefix to the header name
|
||||
*/
|
||||
private String addPrefixIfNecessary(String prefix, String propertyName) {
|
||||
String headerName = propertyName;
|
||||
if (StringUtils.hasText(prefix) && !headerName.startsWith(prefix)) {
|
||||
headerName = prefix + propertyName;
|
||||
}
|
||||
return headerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of standard REQUEST headers. Implementation provided by a subclass
|
||||
*/
|
||||
protected List<String> getStandardReplyHeaderNames(){
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PREFIX used by standard headers (if any)
|
||||
*/
|
||||
protected List<String> getStandardRequestHeaderNames(){
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of standard REPLY headers. Implementation provided by a subclass
|
||||
*/
|
||||
protected abstract String getStandardHeaderPrefix();
|
||||
|
||||
protected abstract Map<String, Object> extractStandardHeaders(T source);
|
||||
|
||||
protected abstract Map<String, Object> extractUserDefinedHeaders(T source);
|
||||
|
||||
protected abstract void populateStandardHeaders(Map<String, Object> headers, T target);
|
||||
|
||||
protected abstract void populateUserDefinedHeader(String headerName, Object headerValue, T target);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-2011 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 java.util.Map;
|
||||
|
||||
import org.springframework.integration.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
|
||||
*
|
||||
*/
|
||||
public interface RequestReplyHeaderMapper<T> {
|
||||
|
||||
void fromHeadersToRequest(MessageHeaders headers, T target);
|
||||
|
||||
void fromHeadersToReply(MessageHeaders headers, T target);
|
||||
|
||||
Map<String, Object> toHeadersFromRequest(T source);
|
||||
|
||||
Map<String, Object> toHeadersFromReply(T source);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user