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:
Artem Bilan
2013-09-16 18:51:36 +03:00
committed by Gary Russell
parent 7c0c9e92fc
commit 7517963be2
17 changed files with 433 additions and 162 deletions

View File

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

View File

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

View File

@@ -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();
}
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
/**
* Contains support classes for Transformers.
*
* @since 3.0
*
*/
package org.springframework.integration.transformer.support;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 '@&lt;beanName&gt;.&lt;beanProperty&gt;'
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 '@&lt;beanName&gt;.&lt;beanProperty&gt;'
SpEL syntax).
Note, similar to the <code>&lt;header-enricher&gt;</code>, the <code>&lt;enricher&gt;</code>'s
<code>header</code> element has <code>type</code> and <code>overwrite</code> attributes.
However, a difference is that, with the <code>&lt;enricher&gt;</code>,
the <code>overwrite</code> attribute is <code>true</code> by default,
to be consistent with <code>&lt;enricher&gt;</code>'s
<code>&lt;property&gt;</code> sub-element.
</para>
</callout>
</calloutlist>
</para>

View File

@@ -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>&lt;header/&gt;</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>