INT-880 Added SpEL support to <header-enricher/> by including an "expression" attribute on the <header/> sub-element (to be used in place of either "value" or "ref").

This commit is contained in:
Mark Fisher
2009-11-25 00:01:31 +00:00
parent 26f257a3b1
commit 63b657b52a
5 changed files with 124 additions and 21 deletions

View File

@@ -28,6 +28,7 @@ import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.transformer.HeaderEnricher.ExpressionHolder;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -105,17 +106,22 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
if (headerName != null) {
String value = headerElement.getAttribute("value");
String ref = headerElement.getAttribute("ref");
String expression = headerElement.getAttribute("expression");
boolean isValue = StringUtils.hasText(value);
boolean isRef = StringUtils.hasText(ref);
if (!(isValue ^ isRef)) {
boolean isExpression = StringUtils.hasText(expression);
if (!(isValue ^ (isRef ^ isExpression))) {
parserContext.getReaderContext().error(
"Exactly one of the 'value' or 'ref' attributes is required.", element);
"Exactly one of the 'ref', 'value', or 'expression' attributes is required.", element);
}
if (isValue) {
Object headerValue = (headerType != null) ?
new TypedStringValue(value, headerType) : value;
headers.put(headerName, headerValue);
}
else if (isExpression) {
headers.put(headerName, new ExpressionHolder(expression, headerType));
}
else {
headers.put(headerName, new RuntimeBeanReference(ref));
}

View File

@@ -16,8 +16,19 @@
package org.springframework.integration.transformer;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.util.Assert;
/**
@@ -28,7 +39,7 @@ import org.springframework.util.Assert;
*
* @author Mark Fisher
*/
public class HeaderEnricher extends AbstractHeaderTransformer {
public class HeaderEnricher implements Transformer {
private final Map<String, Object> headersToAdd;
@@ -48,13 +59,60 @@ public class HeaderEnricher extends AbstractHeaderTransformer {
this.overwrite = overwrite;
}
@Override
protected final void transformHeaders(Map<String, Object> headers) {
for (Map.Entry<String, Object> entry : this.headersToAdd.entrySet()) {
String key = entry.getKey();
if (this.overwrite || headers.get(key) == null) {
headers.put(key, entry.getValue());
public Message<?> transform(Message<?> message) {
try {
Map<String, Object> headerMap = new HashMap<String, Object>(message.getHeaders());
for (Map.Entry<String, Object> entry : this.headersToAdd.entrySet()) {
String key = entry.getKey();
if (this.overwrite || headerMap.get(key) == null) {
Object value = entry.getValue();
if (value instanceof ExpressionHolder) {
value = ((ExpressionHolder) value).evaluate(message);
}
headerMap.put(key, value);
}
}
return MessageBuilder.withPayload(message.getPayload()).copyHeaders(headerMap).build();
}
catch (Exception e) {
throw new MessagingException(message, "failed to transform message headers", e);
}
}
public static class ExpressionHolder {
private static final ExpressionParser parser = new SpelExpressionParser();
private final String expressionString;
private final Class<?> expectedType;
private volatile Expression parsedExpression;
/**
* Create a holder object for the given expression String and the expected type
* of the expression evaluation result. The expectedType may be null if unknown.
*/
public ExpressionHolder(String expressionString, Class<?> expectedType) {
this.expressionString = expressionString;
this.expectedType = expectedType;
}
private Object evaluate(Message<?> message) throws ParseException, EvaluationException {
if (this.parsedExpression == null) {
synchronized (this) {
this.parsedExpression = parser.parseExpression(this.expressionString);
}
}
StandardEvaluationContext context = new StandardEvaluationContext(message);
context.addPropertyAccessor(new MapAccessor());
return (this.expectedType != null)
? this.parsedExpression.getValue(context, this.expectedType)
: this.parsedExpression.getValue(context);
}
}

View File

@@ -901,7 +901,7 @@
</xsd:element>
<xsd:element name="poller" type="innerPollerType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attributeGroup ref="headerEnricherAttributes" />
<xsd:attribute name="overwrite" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -946,7 +946,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<!-- TODO: xsd:attribute name="expression" type="xsd:string" / -->
<xsd:attribute name="expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Expression to be evaulated at runtime to determine the header value.
The EvaluationContext will include variables for 'payload' and 'headers'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -961,16 +968,6 @@
</xsd:attribute>
</xsd:complexType>
<xsd:attributeGroup name="headerEnricherAttributes">
<xsd:annotation>
<xsd:documentation>
Provides the names of the standard configurable
MessageHeaders.
</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="overwrite" type="xsd:string" />
</xsd:attributeGroup>
<xsd:element name="thread-pool-task-executor">
<xsd:complexType>
<xsd:annotation>

View File

@@ -62,4 +62,12 @@
<priority value="HIGH"/>
</header-enricher>
<header-enricher input-channel="payloadExpressionInput">
<header name="testHeader" expression="payload.name + 'bar'"/>
</header-enricher>
<header-enricher input-channel="headerExpressionInput">
<header name="testHeader2" expression="headers.testHeader1 + 'bar'"/>
</header-enricher>
</beans:beans>

View File

@@ -29,6 +29,7 @@ import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessagePriority;
import org.springframework.integration.gateway.SimpleMessagingGateway;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.StringMessage;
import org.springframework.integration.transformer.MessageTransformationException;
import org.springframework.test.context.ContextConfiguration;
@@ -115,4 +116,37 @@ public class HeaderEnricherTests {
assertEquals(MessagePriority.HIGH, result.getHeaders().getPriority());
}
@Test
public void expressionUsingPayload() {
SimpleMessagingGateway gateway = new SimpleMessagingGateway();
gateway.setRequestChannel(context.getBean("payloadExpressionInput", MessageChannel.class));
Message<?> result = gateway.sendAndReceiveMessage(new TestBean("foo"));
assertNotNull(result);
assertEquals("foobar", result.getHeaders().get("testHeader"));
}
@Test
public void expressionUsingHeader() {
SimpleMessagingGateway gateway = new SimpleMessagingGateway();
gateway.setRequestChannel(context.getBean("headerExpressionInput", MessageChannel.class));
Message<?> message = MessageBuilder.withPayload("test").setHeader("testHeader1", "foo").build();
Message<?> result = gateway.sendAndReceiveMessage(message);
assertNotNull(result);
assertEquals("foobar", result.getHeaders().get("testHeader2"));
}
public static class TestBean {
private final String name;
TestBean(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
}