INT-3027: Headers Enrichment for <enricher>
* Add `<header>` sub-element to `<enricher>` * Refactoring of `EnricherParser` * Add Headers Enrichment logic to `ContentEnricher` * Add tests * What's new and Content Enricher's `<header>` note JIRA: https://jira.springsource.org/browse/INT-3027 INT-3027: Addressing PR comments INT-3027: add NPE asserts to map properties INT-3027: add overwrite & type to enricher header Move `HeaderValueMessageProcessor` hierarchy to 'transformer.support' package INT-3027: Make header's overwrite=true by default Doc Polishing
This commit is contained in:
committed by
Gary Russell
parent
7c0c9e92fc
commit
7517963be2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,10 +20,10 @@ import java.util.List;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.transformer.ContentEnricher;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -34,6 +34,7 @@ import org.springframework.util.xml.DomUtils;
|
||||
* Parser for the 'enricher' element.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @since 2.1
|
||||
*/
|
||||
public class EnricherParser extends AbstractConsumerEndpointParser {
|
||||
@@ -47,34 +48,34 @@ public class EnricherParser extends AbstractConsumerEndpointParser {
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-timeout");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply");
|
||||
|
||||
List<Element> propertyElements = DomUtils.getChildElementsByTagName(element, "property");
|
||||
if (!CollectionUtils.isEmpty(propertyElements)) {
|
||||
ManagedMap<String, Object> propertyExpressions = new ManagedMap<String, Object>();
|
||||
for (Element propertyElement : propertyElements) {
|
||||
String name = propertyElement.getAttribute("name");
|
||||
String value = propertyElement.getAttribute("value");
|
||||
String expression = propertyElement.getAttribute("expression");
|
||||
if (StringUtils.hasText(value) && StringUtils.hasText(expression)) {
|
||||
parserContext.getReaderContext().error("The 'value' and 'expression' attributes are mutually exclusive on " +
|
||||
"an <enricher> element's <property> sub-element.", parserContext.extractSource(propertyElement));
|
||||
}
|
||||
if (StringUtils.hasText(value)) {
|
||||
BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(LiteralExpression.class);
|
||||
expressionBuilder.addConstructorArgValue(value);
|
||||
propertyExpressions.put(name, expressionBuilder.getBeanDefinition());
|
||||
}
|
||||
else if (StringUtils.hasText(expression)) {
|
||||
BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class);
|
||||
expressionBuilder.addConstructorArgValue(expression);
|
||||
propertyExpressions.put(name, expressionBuilder.getBeanDefinition());
|
||||
}
|
||||
else {
|
||||
parserContext.getReaderContext().error("Exactly one of 'value' or 'expression' attributes must be provided on " +
|
||||
"an <enricher> element's <property> sub-element.", parserContext.extractSource(propertyElement));
|
||||
}
|
||||
|
||||
List<Element> subElements = DomUtils.getChildElementsByTagName(element, "property");
|
||||
if (!CollectionUtils.isEmpty(subElements)) {
|
||||
ManagedMap<String, Object> expressions = new ManagedMap<String, Object>();
|
||||
for (Element subElement : subElements) {
|
||||
String name = subElement.getAttribute("name");
|
||||
BeanDefinition beanDefinition = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("value",
|
||||
"expression", parserContext, subElement, true);
|
||||
expressions.put(name, beanDefinition);
|
||||
}
|
||||
builder.addPropertyValue("propertyExpressions", propertyExpressions);
|
||||
builder.addPropertyValue("propertyExpressions", expressions);
|
||||
}
|
||||
|
||||
subElements = DomUtils.getChildElementsByTagName(element, "header");
|
||||
if (!CollectionUtils.isEmpty(subElements)) {
|
||||
ManagedMap<String, Object> expressions = new ManagedMap<String, Object>();
|
||||
for (Element subElement : subElements) {
|
||||
String name = subElement.getAttribute("name");
|
||||
BeanDefinition expressionDefinition = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("value",
|
||||
"expression", parserContext, subElement, true);
|
||||
BeanDefinitionBuilder valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor");
|
||||
valueProcessorBuilder.addConstructorArgValue(expressionDefinition)
|
||||
.addConstructorArgValue(subElement.getAttribute("type"));
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(valueProcessorBuilder, subElement, "overwrite");
|
||||
expressions.put(name, valueProcessorBuilder.getBeanDefinition());
|
||||
}
|
||||
builder.addPropertyValue("headerExpressions", expressions);
|
||||
}
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "should-clone-payload");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -179,7 +179,7 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
|
||||
Object headerValue = (headerType != null) ?
|
||||
new TypedStringValue(value, headerType) : value;
|
||||
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$StaticHeaderValueMessageProcessor");
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
|
||||
valueProcessorBuilder.addConstructorArgValue(headerValue);
|
||||
}
|
||||
else if (isExpression) {
|
||||
@@ -188,7 +188,7 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
|
||||
"The 'method' attribute cannot be used with the 'expression' attribute.", element);
|
||||
}
|
||||
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$ExpressionEvaluatingHeaderValueMessageProcessor");
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor");
|
||||
if (expressionElement != null) {
|
||||
BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DynamicExpression.class);
|
||||
dynamicExpressionBuilder.addConstructorArgValue(expressionElement.getAttribute("key"));
|
||||
@@ -207,7 +207,7 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
|
||||
}
|
||||
if (hasMethod || isScript) {
|
||||
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$MessageProcessingHeaderValueMessageProcessor");
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
|
||||
valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
|
||||
if (hasMethod) {
|
||||
valueProcessorBuilder.addConstructorArgValue(method);
|
||||
@@ -215,7 +215,7 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
|
||||
}
|
||||
else {
|
||||
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$StaticHeaderValueMessageProcessor");
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
|
||||
valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
|
||||
}
|
||||
}
|
||||
@@ -226,13 +226,13 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
|
||||
}
|
||||
if (hasMethod) {
|
||||
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$MessageProcessingHeaderValueMessageProcessor");
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
|
||||
valueProcessorBuilder.addConstructorArgReference(ref);
|
||||
valueProcessorBuilder.addConstructorArgValue(method);
|
||||
}
|
||||
else {
|
||||
valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$StaticHeaderValueMessageProcessor");
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
|
||||
valueProcessorBuilder.addConstructorArgReference(ref);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.integration.expression.IntegrationEvaluationContextAw
|
||||
import org.springframework.integration.gateway.MessagingGatewaySupport;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.transformer.support.HeaderValueMessageProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -51,7 +52,9 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public class ContentEnricher extends AbstractReplyProducingMessageHandler implements Lifecycle, IntegrationEvaluationContextAware {
|
||||
|
||||
private final Map<Expression, Expression> propertyExpressions = new HashMap<Expression, Expression>();
|
||||
private volatile Map<Expression, Expression> propertyExpressions = new HashMap<Expression, Expression>();
|
||||
|
||||
private volatile Map<String, HeaderValueMessageProcessor<?>> headerExpressions = new HashMap<String, HeaderValueMessageProcessor<?>>();
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||
|
||||
@@ -80,16 +83,28 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
*/
|
||||
public void setPropertyExpressions(Map<String, Expression> propertyExpressions) {
|
||||
Assert.notEmpty(propertyExpressions, "propertyExpressions must not be empty");
|
||||
synchronized (this.propertyExpressions) {
|
||||
this.propertyExpressions.clear();
|
||||
for (Map.Entry<String, Expression> entry : propertyExpressions.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Expression value = entry.getValue();
|
||||
Assert.notNull(key, "propertyExpressions key must not be null");
|
||||
Assert.notNull(value, "propertyExpressions value must not be null");
|
||||
this.propertyExpressions.put(parser.parseExpression(key), value);
|
||||
}
|
||||
Assert.noNullElements(propertyExpressions.keySet().toArray(), "propertyExpressions keys must not be empty");
|
||||
Assert.noNullElements(propertyExpressions.values().toArray(), "propertyExpressions values must not be empty");
|
||||
Map<Expression, Expression> localMap = new HashMap<Expression, Expression>(propertyExpressions.size());
|
||||
for (Map.Entry<String, Expression> entry : propertyExpressions.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Expression value = entry.getValue();
|
||||
localMap.put(parser.parseExpression(key), value);
|
||||
}
|
||||
this.propertyExpressions = localMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the map of {@link HeaderValueMessageProcessor} to evaluate when enriching
|
||||
* the target MessageHeaders.
|
||||
* The keys should simply be header names, and the values should be Expressions
|
||||
* that will evaluate against the reply Message as the root object.
|
||||
*/
|
||||
public void setHeaderExpressions(Map<String, HeaderValueMessageProcessor<?>> headerExpressions) {
|
||||
Assert.notEmpty(headerExpressions, "headerExpressions must not be empty");
|
||||
Assert.noNullElements(headerExpressions.keySet().toArray(), "headerExpressions keys must not be empty");
|
||||
Assert.noNullElements(headerExpressions.values().toArray(), "headerExpressions values must not be empty");
|
||||
this.headerExpressions = new HashMap<String, HeaderValueMessageProcessor<?>>(headerExpressions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +236,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
final Object targetPayload;
|
||||
if (requestPayload instanceof Cloneable && this.shouldClonePayload) {
|
||||
try {
|
||||
Method cloneMethod = requestPayload.getClass().getMethod("clone", new Class<?>[0]);
|
||||
Method cloneMethod = requestPayload.getClass().getMethod("clone");
|
||||
targetPayload = ReflectionUtils.invokeMethod(cloneMethod, requestPayload);
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -256,7 +271,24 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
Object value = valueExpression.getValue(this.sourceEvaluationContext, replyMessage);
|
||||
propertyExpression.setValue(this.targetEvaluationContext, targetPayload, value);
|
||||
}
|
||||
return targetPayload;
|
||||
|
||||
if (this.headerExpressions.isEmpty()) {
|
||||
return targetPayload;
|
||||
}
|
||||
else {
|
||||
Map<String, Object> targetHeaders = new HashMap<String, Object>(this.headerExpressions.size());
|
||||
for (Map.Entry<String, HeaderValueMessageProcessor<?>> entry : this.headerExpressions.entrySet()) {
|
||||
String header = entry.getKey();
|
||||
HeaderValueMessageProcessor<?> valueProcessor = entry.getValue();
|
||||
Boolean overwrite = valueProcessor.isOverwrite();
|
||||
overwrite = overwrite != null ? overwrite : true;
|
||||
if (overwrite || !requestMessage.getHeaders().containsKey(header)) {
|
||||
Object value = valueProcessor.processMessage(replyMessage);
|
||||
targetHeaders.put(header, value);
|
||||
}
|
||||
}
|
||||
return MessageBuilder.withPayload(targetPayload).copyHeaders(targetHeaders).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -21,20 +21,14 @@ import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
|
||||
import org.springframework.integration.handler.MessageProcessor;
|
||||
import org.springframework.integration.handler.MethodInvokingMessageProcessor;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.transformer.support.HeaderValueMessageProcessor;
|
||||
|
||||
/**
|
||||
* A Transformer that adds statically configured header values to a Message.
|
||||
@@ -146,78 +140,6 @@ public class HeaderEnricher implements Transformer, BeanNameAware, InitializingB
|
||||
}
|
||||
}
|
||||
|
||||
public static interface HeaderValueMessageProcessor<T> extends MessageProcessor<T> {
|
||||
|
||||
Boolean isOverwrite();
|
||||
|
||||
}
|
||||
|
||||
static abstract class AbstractHeaderValueMessageProcessor<T> implements HeaderValueMessageProcessor<T> {
|
||||
|
||||
// null indicates no explicit setting; use header-enricher's
|
||||
// 'default-overwrite' value
|
||||
private volatile Boolean overwrite = null;
|
||||
|
||||
public void setOverwrite(Boolean overwrite) {
|
||||
this.overwrite = overwrite;
|
||||
}
|
||||
|
||||
public Boolean isOverwrite() {
|
||||
return this.overwrite;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class StaticHeaderValueMessageProcessor<T> extends AbstractHeaderValueMessageProcessor<T> {
|
||||
|
||||
private final T value;
|
||||
|
||||
public StaticHeaderValueMessageProcessor(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public T processMessage(Message<?> message) {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
static class ExpressionEvaluatingHeaderValueMessageProcessor<T> extends AbstractHeaderValueMessageProcessor<T>
|
||||
implements BeanFactoryAware {
|
||||
|
||||
private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(
|
||||
true, true));
|
||||
|
||||
private final ExpressionEvaluatingMessageProcessor<T> targetProcessor;
|
||||
|
||||
/**
|
||||
* Create a header value processor for the given Expression and the
|
||||
* expected type of the expression evaluation result. The expectedType
|
||||
* may be null if unknown.
|
||||
*/
|
||||
public ExpressionEvaluatingHeaderValueMessageProcessor(Expression expression, Class<T> expectedType) {
|
||||
this.targetProcessor = new ExpressionEvaluatingMessageProcessor<T>(expression, expectedType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a header value processor for the given expression string and
|
||||
* the expected type of the expression evaluation result. The
|
||||
* expectedType may be null if unknown.
|
||||
*/
|
||||
public ExpressionEvaluatingHeaderValueMessageProcessor(String expressionString, Class<T> expectedType) {
|
||||
Expression expression = expressionParser.parseExpression(expressionString);
|
||||
this.targetProcessor = new ExpressionEvaluatingMessageProcessor<T>(expression, expectedType);
|
||||
}
|
||||
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.targetProcessor.setBeanFactory(beanFactory);
|
||||
}
|
||||
|
||||
public T processMessage(Message<?> message) {
|
||||
return this.targetProcessor.processMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
@@ -241,7 +163,7 @@ public class HeaderEnricher implements Transformer, BeanNameAware, InitializingB
|
||||
for (HeaderValueMessageProcessor<?> processor : this.headersToAdd.values()) {
|
||||
Boolean processerOverwrite = processor.isOverwrite();
|
||||
if (processerOverwrite != null) {
|
||||
shouldOverwrite |= processerOverwrite.booleanValue();
|
||||
shouldOverwrite |= processerOverwrite;
|
||||
}
|
||||
}
|
||||
if (!shouldOverwrite && !this.shouldSkipNulls) {
|
||||
@@ -250,26 +172,4 @@ public class HeaderEnricher implements Transformer, BeanNameAware, InitializingB
|
||||
}
|
||||
}
|
||||
|
||||
static class MessageProcessingHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor<Object> {
|
||||
|
||||
private final MessageProcessor<?> targetProcessor;
|
||||
|
||||
public <T> MessageProcessingHeaderValueMessageProcessor(MessageProcessor<T> targetProcessor) {
|
||||
this.targetProcessor = targetProcessor;
|
||||
}
|
||||
|
||||
public MessageProcessingHeaderValueMessageProcessor(Object targetObject) {
|
||||
this(targetObject, null);
|
||||
}
|
||||
|
||||
public MessageProcessingHeaderValueMessageProcessor(Object targetObject, String method) {
|
||||
this.targetProcessor = new MethodInvokingMessageProcessor<Object>(targetObject, method);
|
||||
}
|
||||
|
||||
public Object processMessage(Message<?> message) {
|
||||
return this.targetProcessor.processMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2013 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.transformer.support;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
abstract class AbstractHeaderValueMessageProcessor<T> implements HeaderValueMessageProcessor<T> {
|
||||
|
||||
// null indicates no explicit setting
|
||||
private volatile Boolean overwrite = null;
|
||||
|
||||
public void setOverwrite(Boolean overwrite) {
|
||||
this.overwrite = overwrite;
|
||||
}
|
||||
|
||||
public Boolean isOverwrite() {
|
||||
return this.overwrite;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2013 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.transformer.support;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
class ExpressionEvaluatingHeaderValueMessageProcessor<T> extends AbstractHeaderValueMessageProcessor<T>
|
||||
implements BeanFactoryAware {
|
||||
|
||||
private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(
|
||||
true, true));
|
||||
|
||||
private final ExpressionEvaluatingMessageProcessor<T> targetProcessor;
|
||||
|
||||
/**
|
||||
* Create a header value processor for the given Expression and the
|
||||
* expected type of the expression evaluation result. The expectedType
|
||||
* may be null if unknown.
|
||||
*/
|
||||
public ExpressionEvaluatingHeaderValueMessageProcessor(Expression expression, Class<T> expectedType) {
|
||||
this.targetProcessor = new ExpressionEvaluatingMessageProcessor<T>(expression, expectedType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a header value processor for the given expression string and
|
||||
* the expected type of the expression evaluation result. The
|
||||
* expectedType may be null if unknown.
|
||||
*/
|
||||
public ExpressionEvaluatingHeaderValueMessageProcessor(String expressionString, Class<T> expectedType) {
|
||||
Expression expression = expressionParser.parseExpression(expressionString);
|
||||
this.targetProcessor = new ExpressionEvaluatingMessageProcessor<T>(expression, expectedType);
|
||||
}
|
||||
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.targetProcessor.setBeanFactory(beanFactory);
|
||||
}
|
||||
|
||||
public T processMessage(Message<?> message) {
|
||||
return this.targetProcessor.processMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2013 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.transformer.support;
|
||||
|
||||
import org.springframework.integration.handler.MessageProcessor;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface HeaderValueMessageProcessor<T> extends MessageProcessor<T> {
|
||||
|
||||
Boolean isOverwrite();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2013 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.transformer.support;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.handler.MessageProcessor;
|
||||
import org.springframework.integration.handler.MethodInvokingMessageProcessor;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
class MessageProcessingHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor<Object> {
|
||||
|
||||
private final MessageProcessor<?> targetProcessor;
|
||||
|
||||
public <T> MessageProcessingHeaderValueMessageProcessor(MessageProcessor<T> targetProcessor) {
|
||||
this.targetProcessor = targetProcessor;
|
||||
}
|
||||
|
||||
public MessageProcessingHeaderValueMessageProcessor(Object targetObject) {
|
||||
this(targetObject, null);
|
||||
}
|
||||
|
||||
public MessageProcessingHeaderValueMessageProcessor(Object targetObject, String method) {
|
||||
this.targetProcessor = new MethodInvokingMessageProcessor<Object>(targetObject, method);
|
||||
}
|
||||
|
||||
public Object processMessage(Message<?> message) {
|
||||
return this.targetProcessor.processMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2013 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.transformer.support;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
class StaticHeaderValueMessageProcessor<T> extends AbstractHeaderValueMessageProcessor<T> {
|
||||
|
||||
private final T value;
|
||||
|
||||
public StaticHeaderValueMessageProcessor(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public T processMessage(Message<?> message) {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Contains support classes for Transformers.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
package org.springframework.integration.transformer.support;
|
||||
@@ -1229,6 +1229,42 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="header" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Each header sub-element provides the name of a message header (via the required 'name' attribute).
|
||||
Exactly one of the 'value' or 'expression' attributes must be provided as well.
|
||||
The former for a literal value to set, and the latter for a SpEL expression to be evaluated.
|
||||
The root object of the evaluation context is the Message that was returned from the flow initiated
|
||||
by this enricher.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType >
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="propertySubElementType">
|
||||
<xsd:attribute name="overwrite" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Boolean value to indicate whether this header value should overwrite an
|
||||
existing header value. Unlike the Header Enricher, this attribute is 'true'
|
||||
by default, similar to the 'property' attribute.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string" />
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="type" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:java.lang.Class">
|
||||
The fully qualified class name of the header value's expected type.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="request-handler-advice-chain" type="adviceChainType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="request-channel" type="xsd:string" use="optional">
|
||||
|
||||
@@ -15,19 +15,28 @@
|
||||
|
||||
<channel id="requests"/>
|
||||
|
||||
<enricher id="enricher" input-channel="input"
|
||||
<enricher id="enricher" input-channel="input"
|
||||
request-channel="requests" request-timeout="1234"
|
||||
reply-timeout="9876"
|
||||
order="99" should-clone-payload="true" output-channel="output">
|
||||
<property name="name" expression="payload.sourceName"/>
|
||||
<property name="age" value="42"/>
|
||||
<property name="gender" expression="@testBean"/>
|
||||
|
||||
|
||||
<header name="foo" value="bar"/>
|
||||
<header name="testBean" expression="@testBean"/>
|
||||
<header name="sourceName" expression="payload.sourceName"/>
|
||||
<header name="notOverwrite" expression="payload.sourceName" overwrite="false"/>
|
||||
|
||||
<request-handler-advice-chain>
|
||||
<beans:bean class="org.springframework.integration.config.xml.EnricherParserTests$FooAdvice" />
|
||||
</request-handler-advice-chain>
|
||||
</enricher>
|
||||
|
||||
<enricher input-channel="input2" output-channel="output">
|
||||
<header name="foo" expression="new java.util.Date()" type="int"/>
|
||||
</enricher>
|
||||
|
||||
<beans:bean id="testBean" class="java.lang.String">
|
||||
<beans:constructor-arg value="male"/>
|
||||
</beans:bean>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2013 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,23 +19,30 @@ package org.springframework.integration.config.xml;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessageHandlingException;
|
||||
import org.springframework.integration.MessageHeaders;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.integration.core.SubscribableChannel;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.integration.transformer.ContentEnricher;
|
||||
@@ -46,6 +53,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* @author Mark Fisher
|
||||
* @author Gunnar Hillert
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
@@ -124,7 +132,10 @@ public class EnricherParserTests {
|
||||
}
|
||||
});
|
||||
Target original = new Target();
|
||||
Message<?> request = MessageBuilder.withPayload(original).build();
|
||||
Message<?> request = MessageBuilder.withPayload(original)
|
||||
.setHeader("sourceName", "test")
|
||||
.setHeader("notOverwrite", "test")
|
||||
.build();
|
||||
context.getBean("input", MessageChannel.class).send(request);
|
||||
Message<?> reply = context.getBean("output", PollableChannel.class).receive(0);
|
||||
Target enriched = (Target) reply.getPayload();
|
||||
@@ -133,6 +144,26 @@ public class EnricherParserTests {
|
||||
assertEquals("male", enriched.getGender());
|
||||
assertNotSame(original, enriched);
|
||||
assertEquals(1, adviceCalled);
|
||||
|
||||
MessageHeaders headers = reply.getHeaders();
|
||||
assertEquals("bar", headers.get("foo"));
|
||||
assertEquals("male", headers.get("testBean"));
|
||||
assertEquals("foo", headers.get("sourceName"));
|
||||
assertEquals("test", headers.get("notOverwrite"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt3027WrongHeaderType() {
|
||||
MessageChannel input = context.getBean("input2", MessageChannel.class);
|
||||
try {
|
||||
input.send(new GenericMessage<Object>("test"));
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e, Matchers.instanceOf(MessageHandlingException.class));
|
||||
assertThat(e.getCause(), Matchers.instanceOf(TypeMismatchException.class));
|
||||
assertThat(e.getCause().getMessage(),
|
||||
Matchers.startsWith("Failed to convert value of type 'java.util.Date' to required type 'int'"));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Source {
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.groovy.GroovyScriptExecutingMessageProcessor;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.integration.transformer.HeaderEnricher;
|
||||
import org.springframework.integration.transformer.support.HeaderValueMessageProcessor;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -71,11 +71,11 @@ public class GroovyHeaderEnricherTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void inlineScript() throws Exception{
|
||||
Map<String, HeaderEnricher.HeaderValueMessageProcessor<?>> headers =
|
||||
Map<String, HeaderValueMessageProcessor<?>> headers =
|
||||
TestUtils.getPropertyValue(headerEnricherWithInlineGroovyScript, "handler.transformer.headersToAdd", Map.class);
|
||||
assertEquals(1, headers.size());
|
||||
HeaderEnricher.HeaderValueMessageProcessor<?> headerValueMessageProcessor = headers.get("TEST_HEADER");
|
||||
assertThat(headerValueMessageProcessor.getClass().getName(), Matchers.containsString("HeaderEnricher$MessageProcessingHeaderValueMessageProcessor"));
|
||||
HeaderValueMessageProcessor<?> headerValueMessageProcessor = headers.get("TEST_HEADER");
|
||||
assertThat(headerValueMessageProcessor.getClass().getName(), Matchers.containsString("MessageProcessingHeaderValueMessageProcessor"));
|
||||
Object targetProcessor = TestUtils.getPropertyValue(headerValueMessageProcessor, "targetProcessor");
|
||||
assertEquals(GroovyScriptExecutingMessageProcessor.class, targetProcessor.getClass());
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -22,6 +22,7 @@ import org.w3c.dom.Node;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.transformer.HeaderEnricher;
|
||||
import org.springframework.integration.transformer.support.HeaderValueMessageProcessor;
|
||||
import org.springframework.integration.xml.DefaultXmlPayloadConverter;
|
||||
import org.springframework.integration.xml.XmlPayloadConverter;
|
||||
import org.springframework.integration.xml.xpath.XPathEvaluationType;
|
||||
@@ -33,7 +34,7 @@ import org.springframework.xml.xpath.XPathExpressionFactory;
|
||||
* Transformer implementation that evaluates XPath expressions against the
|
||||
* message payload and inserts the result of the evaluation into a message
|
||||
* header. The header names will match the keys in the map of expressions.
|
||||
*
|
||||
*
|
||||
* @author Jonas Partner
|
||||
* @author Mark Fisher
|
||||
* @since 2.0
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
Please go to the adapter specific sections of this reference manual
|
||||
to learn more about those adapters.
|
||||
</para>
|
||||
<para>
|
||||
For more information regarding expressions support, please see <xref linkend="spel" />.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="header-enricher">
|
||||
@@ -227,6 +230,8 @@
|
||||
<int:poller></int:poller> ]]><co id="payload-enricher10-co" linkends="payload-enricher10" /><![CDATA[
|
||||
<int:property name="" expression=""/> ]]><co id="payload-enricher11-co" linkends="payload-enricher11" /><![CDATA[
|
||||
<int:property name="" value=""/>
|
||||
<int:header name="" expression=""/> ]]><co id="payload-enricher12-co" linkends="payload-enricher12" /><![CDATA[
|
||||
<int:header name="" value="" overwrite="" type=""/>
|
||||
</int:enricher>]]></programlisting>
|
||||
|
||||
<para>
|
||||
@@ -358,6 +363,27 @@
|
||||
application context (using the '@<beanName>.<beanProperty>'
|
||||
SpEL syntax).
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="payload-enricher12-co" id="payload-enricher12">
|
||||
<para>
|
||||
Each <code>header</code> sub-element provides the
|
||||
name of a Message header (via the mandatory <code>name</code>
|
||||
attribute). Exactly one of the <code>value</code>
|
||||
or <code>expression</code> attributes must be provided
|
||||
as well. The former for a literal value to set, and the
|
||||
latter for a SpEL expression to be evaluated. The root
|
||||
object of the evaluation context is the Message that was
|
||||
returned from the flow initiated by this enricher, the
|
||||
input Message if there is no request channel, or the
|
||||
application context (using the '@<beanName>.<beanProperty>'
|
||||
SpEL syntax).
|
||||
Note, similar to the <code><header-enricher></code>, the <code><enricher></code>'s
|
||||
<code>header</code> element has <code>type</code> and <code>overwrite</code> attributes.
|
||||
However, a difference is that, with the <code><enricher></code>,
|
||||
the <code>overwrite</code> attribute is <code>true</code> by default,
|
||||
to be consistent with <code><enricher></code>'s
|
||||
<code><property></code> sub-element.
|
||||
</para>
|
||||
</callout>
|
||||
</calloutlist>
|
||||
</para>
|
||||
|
||||
@@ -86,6 +86,14 @@
|
||||
<interfacename>MessageSource</interfacename>; see <xref linkend="channel-adapter-expressions-and-scripts"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-content-enricher-headers">
|
||||
<title>Content Enricher: Headers Enrichment Support</title>
|
||||
<para>
|
||||
The Content Enricher now provides configuration for <code><header/></code>
|
||||
sub-elements, to enrich the outbound Message with headers based on the reply Message from the underlying
|
||||
message flow. For more information see <xref linkend="payload-enricher"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-spel-customization">
|
||||
<title>Spring Expression Language (SpEL) Configuration</title>
|
||||
<para>
|
||||
|
||||
Reference in New Issue
Block a user