diff --git a/docs/src/reference/docbook/content-enrichment.xml b/docs/src/reference/docbook/content-enrichment.xml
index 1bebdd58fe..0d35e186f5 100644
--- a/docs/src/reference/docbook/content-enrichment.xml
+++ b/docs/src/reference/docbook/content-enrichment.xml
@@ -1,115 +1,168 @@
- Content Enricher
+ Content Enricher
+
+
+ Introduction
+
+ At times you may have a requirement to enhance a request with more
+ information than was provided by the target system. The
+ Content Enricher
+ pattern describes various scenarios as well as the component
+ (Enricher), which allows you to address such requirements.
+
+
+ The Spring Integration Core module includes 2 enrichers:
+
+
+ Header Enricher
+ (Generic) Enricher
+
+
+ Furthermore, several Adapter specific Header Enrichers
+ are included as well:
+
+
+ XPath Header Enricher (XML Module)
+ Email Header Enricher (Mail Module)
+ XMPP Header Enricher (XMPP Module)
+
+
+ Please go to the adapter specific sections of this reference manual
+ to learn more about those adapters.
+
+
+
+
+ Header Enricher
+
+
+ If you only need to add headers to a Message, and they are not
+ dynamically determined by the Message content, then referencing a
+ custom implementation of a Transformer may be overkill. For that reason,
+ Spring Integration provides support for the Header Enricher
+ pattern. It is exposed via the <header-enricher> element.
+
-
- Introduction
-
- At times you may have a requirement to enhance a request with more information than was
- provided by the target system. The Content Enricher pattern
- describes various scenarios as well as the component (Enricher), which allows you to address such requirements.
-
-
-
-
- Header Enricher
-
-
- If you only need to add headers to a Message, and they are not dynamically determined by the Message content,
- then referencing a custom implementation of a Transformer may be overkill. For that reason,
- Spring Integration provides support for the Header Enricher pattern. It is exposed via
- the <header-enricher> element.
-
]]>
-
-
- The Header Enricher also provides helpful sub-elements to set well-known header names.
-
+
+ The Header Enricher also provides helpful sub-elements
+ to set well-known header names.
+
+
+
]]>
-
- In the above configuration you can clearly see that for well-known headers such as errorChannel,
- correlationId, priority, replyChanneletc., instead of using generic
- <header> sub-elements where you would have to provide both header 'name' and 'value',
- you can use convenient sub-elements to set those values directly.
-
-
- POJO Support
-
-
- Often a header value cannot be defined statically and has to be determined dynamically based on some content in the Message. That is why
- Header Enricher allows you to also specify a bean 'ref' and 'method' that will calculate the
- header value. Let's look at the following configuration:
+
+ In the above configuration you can clearly see that for well-known
+ headers such as errorChannel, correlationId,
+ priority, replyChanneletc., instead of
+ using generic <header> sub-elements where
+ you would have to provide both header 'name' and 'value', you can use
+ convenient sub-elements to set those values directly.
+
-
+
+ POJO Support
+
+
+
+ Often a header value cannot be defined statically and has to be
+ determined dynamically based on some content in the Message. That is why
+ Header Enricher allows you to also specify a bean
+ reference using the ref and method attribute.
+ The specified method will calculate the header value. Let's look at
+ the following configuration:
+
+
+ ]]>
-
-
-
- You can also configure your POJO as inner bean
+
+
+ You can also configure your POJO as inner bean:
+
]]>
+
+ as well as point to a Groovy script:
+
-as well as point to a Groovy script
-
-
+
]]>
-
+
+ SpEL Support
+
+
+ In Spring Integration 2.0 we have introduced the convenience of the
+ Spring Expression Language (SpEL)
+ to help configure many different components. The Header
+ Enricher is one of them.
-
- SpEL Support
-
-
- In Spring Integration 2.0 we have introduced the convenience of the
- Spring Expression Language (SpEL)
- to help configure many different components. The Header Enricher is one of them.
+ Looking again at the POJO example above, you can see that the computation
+ logic to determine the header value is actually pretty simple. A natural
+ question would be: "is there a simpler way to accomplish this?". That
+ is where SpEL shows its true power.
+
- Looking again at the POJO example above, you can see that the computation logic to determine the header value is actually pretty simple.
- A natural question would be: "is there a simpler way to accomplish this?". That is where SpEL shows its true power.
-
-
+
]]>
- As you can see, by using SpEL for such simple cases, we no longer have to provide a separate class and configure
- it in the application context. All we need is the expression attribute configured with a valid
- SpEL expression. The 'payload' and 'headers' variables are bound to the SpEL Evaluation Context,
- giving you full access to the incoming Message.
-
-
- Adapter specific Header Enrichers
-
-
- As you go through the manual, you will see that as an added convenience,
- Spring Integration also provides adapter specific Header Enrichers (e.g., MAIL, XMPP, etc.)
-
-
+
+ As you can see, by using SpEL for such simple cases, we no longer have
+ to provide a separate class and configure it in the application context.
+ All we need is the expression attribute configured
+ with a valid SpEL expression. The 'payload' and 'headers' variables
+ are bound to the SpEL Evaluation Context, giving you full access to
+ the incoming Message.
+
+
+
+
+ (Generic) Enricher
+
+
+
+
+
+
+]]>
+
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/EnricherParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/EnricherParser.java
index 075f1b0080..c56e999fab 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/EnricherParser.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/EnricherParser.java
@@ -32,7 +32,7 @@ import org.springframework.util.xml.DomUtils;
/**
* Parser for the 'enricher' element.
- *
+ *
* @author Mark Fisher
* @since 2.1
*/
@@ -75,7 +75,17 @@ public class EnricherParser extends AbstractConsumerEndpointParser {
}
builder.addPropertyValue("propertyExpressions", propertyExpressions);
}
+
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "should-clone-payload");
+
+ String requestPayloadExpression = element.getAttribute("request-payload-expression");
+
+ if (StringUtils.hasText(requestPayloadExpression)) {
+ BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class);
+ expressionBuilder.addConstructorArgValue(requestPayloadExpression);
+ builder.addPropertyValue("requestPayloadExpression", expressionBuilder.getBeanDefinition());
+ }
+
return builder;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java
index a11abf4125..fe693d3bd6 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java
@@ -31,6 +31,7 @@ import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
+import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@@ -53,6 +54,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
private volatile boolean shouldClonePayload = false;
+ private Expression requestPayloadExpression;
/**
* Create a Content Enricher with the given request channel. An anonymous reply channel
@@ -94,6 +96,34 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
}
}
+ /**
+ * By default the original message's payload will be used as the actual payload
+ * that will be send to the request-channel.
+ *
+ * By providing a SpEL expression as value for this setter, a subset of the
+ * original payload, a header value or any other resolvable SpEL expression
+ * can be used as the basis for the payload, that will be send to the
+ * request-channel.
+ *
+ * For the Expression evaluation the full message is available as the root object.
+ *
+ * For instance the following SpEL expressions (among others) are possible:
+ *
+ *
+ *
payload.foo
+ *
headers.foobar
+ *
new java.util.Date()
+ *
'foo' + 'bar'
+ *
+ *
+ * If more sophisticated logic is required (e.g. changing the message
+ * headers etc.) please use additional downstream transformers.
+ *
+ */
+ public void setRequestPayloadExpression(Expression requestPayloadExpression) {
+ this.requestPayloadExpression = requestPayloadExpression;
+ }
+
/**
* Specify whether to clone payload objects to create the target object.
* This is only applicable for payload types that implement Cloneable.
@@ -110,23 +140,46 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
@Override
protected Object handleRequestMessage(Message> requestMessage) {
- Object targetPayload = requestMessage.getPayload();
- if (targetPayload instanceof Cloneable && this.shouldClonePayload) {
+
+ final Object requestPayload = requestMessage.getPayload();
+
+ final Object targetPayload;
+
+ if (requestPayload instanceof Cloneable && this.shouldClonePayload) {
try {
- Method cloneMethod = targetPayload.getClass().getMethod("clone", new Class>[0]);
- targetPayload = ReflectionUtils.invokeMethod(cloneMethod, targetPayload);
+ Method cloneMethod = requestPayload.getClass().getMethod("clone", new Class>[0]);
+ targetPayload = ReflectionUtils.invokeMethod(cloneMethod, requestPayload);
}
catch (Exception e) {
throw new MessageHandlingException(requestMessage, "Failed to clone payload object", e);
}
+ } else {
+ targetPayload = requestPayload;
}
- Message> replyMessage = this.gateway.sendAndReceiveMessage(requestMessage);
+
+ final Message> actualRequestMessage;
+
+ if (this.requestPayloadExpression==null) {
+
+ actualRequestMessage = requestMessage;
+
+ } else {
+
+ final Object requestMessagePayload = this.requestPayloadExpression.getValue(this.evaluationContext, requestMessage);
+ actualRequestMessage = MessageBuilder.withPayload(requestMessagePayload)
+ .copyHeaders(requestMessage.getHeaders())
+ .build();
+ }
+
+ final Message> replyMessage = this.gateway.sendAndReceiveMessage(actualRequestMessage);
+
for (Map.Entry entry : this.propertyExpressions.entrySet()) {
Expression propertyExpression = entry.getKey();
Expression valueExpression = entry.getValue();
Object value = valueExpression.getValue(this.evaluationContext, replyMessage);
propertyExpression.setValue(this.evaluationContext, targetPayload, value);
}
+
return targetPayload;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/package-info.java
new file mode 100644
index 0000000000..62c9ce0e22
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * Contains core-implementation of various Transformers which includes Enrichers
+ * and Filters.
+ *
+ * @since 1.0
+ *
+ */
+package org.springframework.integration.transformer;
\ No newline at end of file
diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.1.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.1.xsd
index e9e95326d5..07ec3d65a5 100644
--- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.1.xsd
+++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.1.xsd
@@ -1052,6 +1052,32 @@ endpoint itself is a Polling Consumer for a channel with a queue.
+
+
+
+
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java
index 0a30de97b7..7eb96882e1 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java
@@ -17,6 +17,7 @@
package org.springframework.integration.config.xml;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotSame;
import java.util.Map;
@@ -63,6 +64,8 @@ public class EnricherParserTests {
DirectFieldAccessor accessor = new DirectFieldAccessor(enricher);
assertEquals(context.getBean("output"), accessor.getPropertyValue("outputChannel"));
assertEquals(true, accessor.getPropertyValue("shouldClonePayload"));
+ assertNull(accessor.getPropertyValue("requestPayloadExpression"));
+
Map propertyExpressions = (Map) accessor.getPropertyValue("propertyExpressions");
for (Map.Entry e : propertyExpressions.entrySet()) {
if ("name".equals(e.getKey().getExpressionString())) {
@@ -98,7 +101,7 @@ public class EnricherParserTests {
private static class Source {
-
+
private final String sourceName;
Source(String sourceName) {
@@ -113,23 +116,23 @@ public class EnricherParserTests {
public static class Target implements Cloneable {
-
+
private volatile String name;
-
+
private volatile int age;
-
+
public String getName() {
return name;
}
-
+
public void setName(String name) {
this.name = name;
}
-
+
public int getAge() {
return age;
}
-
+
public void setAge(int age) {
this.age = age;
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserWithRequestPayloadExpressionTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserWithRequestPayloadExpressionTests-context.xml
new file mode 100644
index 0000000000..6c808c411b
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserWithRequestPayloadExpressionTests-context.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserWithRequestPayloadExpressionTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserWithRequestPayloadExpressionTests.java
new file mode 100644
index 0000000000..f8bcb98a77
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserWithRequestPayloadExpressionTests.java
@@ -0,0 +1,160 @@
+/*
+ * 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.config.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertSame;
+
+import java.util.Map;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.DirectFieldAccessor;
+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.core.PollableChannel;
+import org.springframework.integration.core.SubscribableChannel;
+import org.springframework.integration.endpoint.EventDrivenConsumer;
+import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.integration.test.util.TestUtils;
+import org.springframework.integration.transformer.ContentEnricher;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Mark Fisher
+ * @since 2.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class EnricherParserWithRequestPayloadExpressionTests {
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void configurationCheck() {
+ Object endpoint = context.getBean("enricher");
+ assertEquals(EventDrivenConsumer.class, endpoint.getClass());
+ Object handler = TestUtils.getPropertyValue(endpoint, "handler");
+ assertEquals(ContentEnricher.class, handler.getClass());
+ ContentEnricher enricher = (ContentEnricher) handler;
+ assertEquals(99, enricher.getOrder());
+ DirectFieldAccessor accessor = new DirectFieldAccessor(enricher);
+ assertEquals(context.getBean("output"), accessor.getPropertyValue("outputChannel"));
+ assertEquals(false, accessor.getPropertyValue("shouldClonePayload"));
+
+ Expression requestPayloadExpression = (Expression) accessor.getPropertyValue("requestPayloadExpression");
+ assertEquals("payload.age", requestPayloadExpression.getExpressionString());
+
+ Map propertyExpressions = (Map) accessor.getPropertyValue("propertyExpressions");
+ for (Map.Entry e : propertyExpressions.entrySet()) {
+ if ("name".equals(e.getKey().getExpressionString())) {
+ assertEquals("'Name as SpEL'", e.getValue().getExpressionString());
+ }
+ else if ("age".equals(e.getKey().getExpressionString())) {
+ assertEquals("payload.sourceName", e.getValue().getExpressionString());
+ }
+ else {
+ throw new IllegalStateException("expected 'name' and 'age' only, not: " + e.getKey().getExpressionString());
+ }
+ }
+ }
+
+ @Test
+ public void integrationTest() {
+ SubscribableChannel requests = context.getBean("requests", SubscribableChannel.class);
+ requests.subscribe(new AbstractReplyProducingMessageHandler() {
+ @Override
+ protected Object handleRequestMessage(Message> requestMessage) {
+
+ assertTrue("Expected the payload of the requestMessage to be a String",
+ requestMessage.getPayload() instanceof Integer);
+
+ Integer payload = (Integer) requestMessage.getPayload();
+ assertEquals("Expected value: 99", Integer.valueOf(99), payload);
+
+ return new Source(String.valueOf(payload));
+ }
+ });
+
+ Target original = new Target();
+ original.setAge(99);
+
+ Message> request = MessageBuilder.withPayload(original).build();
+ context.getBean("input", MessageChannel.class).send(request);
+ Message> reply = context.getBean("output", PollableChannel.class).receive(0);
+ Target enriched = (Target) reply.getPayload();
+ assertEquals("Name as SpEL", enriched.getName());
+ assertEquals(99, enriched.getAge());
+ assertSame(original, enriched);
+ }
+
+
+ private static class Source {
+
+ private final String sourceName;
+
+ Source(String sourceName) {
+ this.sourceName = sourceName;
+ }
+
+ @SuppressWarnings("unused")
+ public String getSourceName() {
+ return sourceName;
+ }
+ }
+
+
+ public static class Target implements Cloneable {
+
+ private volatile String name;
+
+ private volatile int age;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public Object clone() {
+ Target copy = new Target();
+ copy.setName(this.name);
+ copy.setAge(this.age);
+ return copy;
+ }
+ }
+
+}