INT-3465:Content Enricher Improvements
JIRA: https://jira.spring.io/browse/INT-3465 Add support for adding/removing individual recipients to the RecipientListRouter Modify documentation in what's new and spring-integration-4.1.xsd Polishing `AbstractRemoteFileOutboundGateway`: close `outputStream` before `file.delete()` to release exclusive file-lock
This commit is contained in:
@@ -24,7 +24,9 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
||||
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.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.integration.transformer.ContentEnricher;
|
||||
@@ -38,6 +40,7 @@ import org.springframework.util.xml.DomUtils;
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Liujiong
|
||||
* @since 2.1
|
||||
*/
|
||||
public class EnricherParser extends AbstractConsumerEndpointParser {
|
||||
@@ -51,29 +54,31 @@ 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> subElements = DomUtils.getChildElementsByTagName(element, "property");
|
||||
if (!CollectionUtils.isEmpty(subElements)) {
|
||||
ManagedMap<String, Object> expressions = new ManagedMap<String, Object>();
|
||||
ManagedMap<String, Object> nullResultExpressions = new ManagedMap<String, Object>();
|
||||
for (Element subElement : subElements) {
|
||||
String name = subElement.getAttribute("name");
|
||||
|
||||
String value = subElement.getAttribute("value");
|
||||
String type = subElement.getAttribute("type");
|
||||
String expression = subElement.getAttribute("expression");
|
||||
|
||||
String nullResultExpression = subElement.getAttribute("null-result-expression");
|
||||
boolean hasAttributeValue = StringUtils.hasText(value);
|
||||
boolean hasAttributeExpression = StringUtils.hasText(expression);
|
||||
boolean hasAttributeNullResultExpression = StringUtils.hasText(nullResultExpression);
|
||||
|
||||
if (hasAttributeValue && hasAttributeExpression){
|
||||
parserContext.getReaderContext().error("Only one of 'value' or 'expression' is allowed", element);
|
||||
}
|
||||
|
||||
if (!hasAttributeValue && !hasAttributeExpression){
|
||||
parserContext.getReaderContext().error("One of 'value' or 'expression' is required", element);
|
||||
if (!hasAttributeValue && !hasAttributeExpression && !hasAttributeNullResultExpression){
|
||||
parserContext.getReaderContext().error("One of 'value' or 'expression' or 'null-result-expression' is required", element);
|
||||
}
|
||||
|
||||
BeanDefinition expressionDef;
|
||||
BeanDefinition expressionDef = null;
|
||||
BeanDefinition nullResultExpressionExpressionDef;
|
||||
|
||||
if (hasAttributeValue) {
|
||||
BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ValueExpression.class);
|
||||
@@ -85,7 +90,7 @@ public class EnricherParser extends AbstractConsumerEndpointParser {
|
||||
}
|
||||
expressionDef = expressionBuilder.getBeanDefinition();
|
||||
}
|
||||
else {
|
||||
else if (hasAttributeExpression) {
|
||||
if (StringUtils.hasText(type)) {
|
||||
parserContext.getReaderContext().error("The 'type' attribute for '<property>' of '<enricher>' " +
|
||||
"is not allowed with an 'expression' attribute.", element);
|
||||
@@ -95,36 +100,83 @@ public class EnricherParser extends AbstractConsumerEndpointParser {
|
||||
.addConstructorArgValue(expression)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
|
||||
|
||||
|
||||
expressions.put(name, expressionDef);
|
||||
if (expressionDef != null){
|
||||
expressions.put(name, expressionDef);
|
||||
}
|
||||
if (hasAttributeNullResultExpression) {
|
||||
nullResultExpressionExpressionDef = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
|
||||
.addConstructorArgValue(nullResultExpression).getBeanDefinition();
|
||||
nullResultExpressions.put(name, nullResultExpressionExpressionDef);
|
||||
}
|
||||
}
|
||||
if (expressions.size() > 0) {
|
||||
builder.addPropertyValue("propertyExpressions", expressions);
|
||||
}
|
||||
if (nullResultExpressions.size() > 0) {
|
||||
builder.addPropertyValue("nullResultPropertyExpressions", nullResultExpressions);
|
||||
}
|
||||
builder.addPropertyValue("propertyExpressions", expressions);
|
||||
}
|
||||
|
||||
subElements = DomUtils.getChildElementsByTagName(element, "header");
|
||||
if (!CollectionUtils.isEmpty(subElements)) {
|
||||
ManagedMap<String, Object> expressions = new ManagedMap<String, Object>();
|
||||
ManagedMap<String, Object> nullResultHeaderExpressions = new ManagedMap<String, Object>();
|
||||
for (Element subElement : subElements) {
|
||||
String name = subElement.getAttribute("name");
|
||||
BeanDefinition expressionDefinition = IntegrationNamespaceUtils
|
||||
.createExpressionDefinitionFromValueOrExpression("value", "expression", parserContext,
|
||||
subElement, true);
|
||||
String nullResultHeaderExpression = subElement.getAttribute("null-result-expression");
|
||||
String valueElementValue = subElement.getAttribute("value");
|
||||
String expressionElementValue = subElement.getAttribute("expression");
|
||||
boolean hasAttributeValue = StringUtils.hasText(valueElementValue);
|
||||
boolean hasAttributeExpression = StringUtils.hasText(expressionElementValue);
|
||||
boolean hasAttributeNullResultExpression = StringUtils.hasText(nullResultHeaderExpression);
|
||||
if (hasAttributeValue && hasAttributeExpression){
|
||||
parserContext.getReaderContext().error("Only one of '" + "value" + "' or '"
|
||||
+ "expression" + "' is allowed", subElement);
|
||||
}
|
||||
|
||||
if (!hasAttributeValue && !hasAttributeExpression && !hasAttributeNullResultExpression){
|
||||
parserContext.getReaderContext().error("One of 'value' or 'expression' or 'null-result-expression' is required", subElement);
|
||||
}
|
||||
BeanDefinition expressionDef = null;
|
||||
if (hasAttributeValue) {
|
||||
expressionDef = new RootBeanDefinition(LiteralExpression.class);
|
||||
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(valueElementValue);
|
||||
}
|
||||
else if (hasAttributeExpression) {
|
||||
expressionDef = IntegrationNamespaceUtils.createExpressionDefIfAttributeDefined("expression", subElement);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(subElement.getAttribute("expression"))
|
||||
&& StringUtils.hasText(subElement.getAttribute("type"))) {
|
||||
parserContext.getReaderContext()
|
||||
.warning("The use of a 'type' attribute is deprecated since 4.0 "
|
||||
+ "when using 'expression'", element);
|
||||
+ "when using 'expression'", subElement);
|
||||
}
|
||||
if (expressionDef != null) {
|
||||
BeanDefinitionBuilder valueProcessorBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ExpressionEvaluatingHeaderValueMessageProcessor.class)
|
||||
.addConstructorArgValue(expressionDef)
|
||||
.addConstructorArgValue(subElement.getAttribute("type"));
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(valueProcessorBuilder, subElement, "overwrite");
|
||||
expressions.put(name, valueProcessorBuilder.getBeanDefinition());
|
||||
}
|
||||
if (hasAttributeNullResultExpression) {
|
||||
BeanDefinition nullResultExpressionDefinition = IntegrationNamespaceUtils
|
||||
.createExpressionDefIfAttributeDefined("null-result-expression", subElement);
|
||||
BeanDefinitionBuilder nullResultValueProcessorBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ExpressionEvaluatingHeaderValueMessageProcessor.class)
|
||||
.addConstructorArgValue(nullResultExpressionDefinition)
|
||||
.addConstructorArgValue(subElement.getAttribute("type"));
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(nullResultValueProcessorBuilder, subElement, "overwrite");
|
||||
nullResultHeaderExpressions.put(name, nullResultValueProcessorBuilder.getBeanDefinition());
|
||||
}
|
||||
BeanDefinitionBuilder valueProcessorBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ExpressionEvaluatingHeaderValueMessageProcessor.class)
|
||||
.addConstructorArgValue(expressionDefinition)
|
||||
.addConstructorArgValue(subElement.getAttribute("type"));
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(valueProcessorBuilder, subElement, "overwrite");
|
||||
expressions.put(name, valueProcessorBuilder.getBeanDefinition());
|
||||
}
|
||||
builder.addPropertyValue("headerExpressions", expressions);
|
||||
if (expressions.size() > 0) {
|
||||
builder.addPropertyValue("headerExpressions", expressions);
|
||||
}
|
||||
if (nullResultHeaderExpressions.size() > 0) {
|
||||
builder.addPropertyValue("nullResultHeaderExpressions", nullResultHeaderExpressions);
|
||||
}
|
||||
}
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "should-clone-payload");
|
||||
|
||||
@@ -40,22 +40,30 @@ import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Content Enricher is a Message Transformer that can augment a message's payload
|
||||
* with either static values or by optionally invoking a downstream message flow
|
||||
* via its request channel and then applying values from the reply Message to the
|
||||
* original payload.
|
||||
* Content Enricher is a Message Transformer that can augment a message's payload with
|
||||
* either static values or by optionally invoking a downstream message flow via its
|
||||
* request channel and then applying values from the reply Message to the original
|
||||
* payload.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gunnar Hillert
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @author Liujiong
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ContentEnricher extends AbstractReplyProducingMessageHandler implements Lifecycle, IntegrationEvaluationContextAware {
|
||||
public class ContentEnricher extends AbstractReplyProducingMessageHandler
|
||||
implements Lifecycle, IntegrationEvaluationContextAware {
|
||||
|
||||
private volatile Map<Expression, Expression> nullResultPropertyExpressions = new HashMap<Expression, Expression>();
|
||||
|
||||
private volatile Map<String, HeaderValueMessageProcessor<?>> nullResultHeaderExpressions =
|
||||
new HashMap<String, HeaderValueMessageProcessor<?>>();
|
||||
|
||||
private volatile Map<Expression, Expression> propertyExpressions = new HashMap<Expression, Expression>();
|
||||
|
||||
private volatile Map<String, HeaderValueMessageProcessor<?>> headerExpressions = new HashMap<String, HeaderValueMessageProcessor<?>>();
|
||||
private volatile Map<String, HeaderValueMessageProcessor<?>> headerExpressions =
|
||||
new HashMap<String, HeaderValueMessageProcessor<?>>();
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||
|
||||
@@ -81,11 +89,25 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
|
||||
private volatile Long replyTimeout;
|
||||
|
||||
public void setNullResultPropertyExpressions(Map<String, Expression> nullResultPropertyExpressions) {
|
||||
Map<Expression, Expression> localMap = new HashMap<Expression, Expression>(nullResultPropertyExpressions.size());
|
||||
for (Map.Entry<String, Expression> entry : nullResultPropertyExpressions.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Expression value = entry.getValue();
|
||||
localMap.put(parser.parseExpression(key), value);
|
||||
}
|
||||
this.nullResultPropertyExpressions = localMap;
|
||||
}
|
||||
|
||||
public void setNullResultHeaderExpressions(Map<String, HeaderValueMessageProcessor<?>> nullResultHeaderExpressions) {
|
||||
this.nullResultHeaderExpressions = new HashMap<String, HeaderValueMessageProcessor<?>>(
|
||||
nullResultHeaderExpressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the map of expressions to evaluate when enriching the target payload.
|
||||
* The keys should simply be property names, and the values should be Expressions
|
||||
* that will evaluate against the reply Message as the root object.
|
||||
*
|
||||
* Provide the map of expressions to evaluate when enriching the target payload. The
|
||||
* keys should simply be property names, and the values should be Expressions that
|
||||
* will evaluate against the reply Message as the root object.
|
||||
* @param propertyExpressions The property expressions.
|
||||
*/
|
||||
public void setPropertyExpressions(Map<String, Expression> propertyExpressions) {
|
||||
@@ -103,10 +125,9 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* @param headerExpressions The header expressions.
|
||||
*/
|
||||
public void setHeaderExpressions(Map<String, HeaderValueMessageProcessor<?>> headerExpressions) {
|
||||
@@ -117,11 +138,10 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content enricher's request channel. If specified, then an internal
|
||||
* Gateway will be initialized. Setting a request channel is optional.
|
||||
* Not setting a request channel is useful in situations where
|
||||
* message payloads shall be enriched with static values only.
|
||||
*
|
||||
* Sets the content enricher's request channel. If specified, then an internal Gateway
|
||||
* will be initialized. Setting a request channel is optional. Not setting a request
|
||||
* channel is useful in situations where message payloads shall be enriched with
|
||||
* static values only.
|
||||
* @param requestChannel The request channel.
|
||||
*/
|
||||
public void setRequestChannel(MessageChannel requestChannel) {
|
||||
@@ -134,9 +154,8 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
|
||||
/**
|
||||
* Sets the content enricher's reply channel. If not specified, yet the request
|
||||
* channel is set, an anonymous reply channel will automatically created
|
||||
* for each request.
|
||||
*
|
||||
* channel is set, an anonymous reply channel will automatically created for each
|
||||
* request.
|
||||
* @param replyChannel The reply channel.
|
||||
*/
|
||||
public void setReplyChannel(MessageChannel replyChannel) {
|
||||
@@ -148,9 +167,8 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout value for sending request messages. If not explicitly
|
||||
* configured, the default is one second.
|
||||
*
|
||||
* Set the timeout value for sending request messages. If not explicitly configured,
|
||||
* the default is one second.
|
||||
* @param requestTimeout the timeout value in milliseconds. Must not be null.
|
||||
*/
|
||||
public void setRequestTimeout(Long requestTimeout) {
|
||||
@@ -159,9 +177,8 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout value for receiving reply messages. If not explicitly
|
||||
* configured, the default is one second.
|
||||
*
|
||||
* Set the timeout value for receiving reply messages. If not explicitly configured,
|
||||
* the default is one second.
|
||||
* @param replyTimeout the timeout value in milliseconds. Must not be null.
|
||||
*/
|
||||
public void setReplyTimeout(Long replyTimeout) {
|
||||
@@ -170,28 +187,27 @@ 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 <b>root object</b>.
|
||||
*
|
||||
* By default the original message's payload will be used as the actual payload that
|
||||
* will be send to the request-channel.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* For the Expression evaluation the full message is available as the <b>root
|
||||
* object</b>.
|
||||
* <p>
|
||||
* For instance the following SpEL expressions (among others) are possible:
|
||||
*
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>payload.foo</li>
|
||||
* <li>headers.foobar</li>
|
||||
* <li>new java.util.Date()</li>
|
||||
* <li>'foo' + 'bar'</li>
|
||||
* <li>payload.foo</li>
|
||||
* <li>headers.foobar</li>
|
||||
* <li>new java.util.Date()</li>
|
||||
* <li>'foo' + 'bar'</li>
|
||||
* </ul>
|
||||
*
|
||||
* If more sophisticated logic is required (e.g. changing the message
|
||||
* headers etc.) please use additional downstream transformers.
|
||||
*
|
||||
* <p>
|
||||
* If more sophisticated logic is required (e.g. changing the message headers etc.)
|
||||
* please use additional downstream transformers.
|
||||
* @param requestPayloadExpression The request payload expression.
|
||||
*
|
||||
*/
|
||||
@@ -200,9 +216,8 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether to clone payload objects to create the target object.
|
||||
* This is only applicable for payload types that implement Cloneable.
|
||||
*
|
||||
* Specify whether to clone payload objects to create the target object. This is only
|
||||
* applicable for payload types that implement Cloneable.
|
||||
* @param shouldClonePayload true if the payload should be cloned.
|
||||
*/
|
||||
public void setShouldClonePayload(boolean shouldClonePayload) {
|
||||
@@ -220,8 +235,8 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Content Enricher. Will instantiate an internal Gateway if
|
||||
* the requestChannel is set.
|
||||
* Initializes the Content Enricher. Will instantiate an internal Gateway if the
|
||||
* requestChannel is set.
|
||||
*/
|
||||
@Override
|
||||
protected void doInit() {
|
||||
@@ -239,16 +254,16 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
Assert.notNull(this.requestChannel, "If the replyChannel is set, then the requestChannel must not be null");
|
||||
}
|
||||
if (this.requestChannel != null) {
|
||||
this.gateway = new Gateway();
|
||||
this.gateway.setRequestChannel(requestChannel);
|
||||
this.gateway = new Gateway();
|
||||
this.gateway.setRequestChannel(requestChannel);
|
||||
|
||||
if (this.requestTimeout != null) {
|
||||
this.gateway.setRequestTimeout(this.requestTimeout);
|
||||
}
|
||||
if (this.requestTimeout != null) {
|
||||
this.gateway.setRequestTimeout(this.requestTimeout);
|
||||
}
|
||||
|
||||
if (this.replyTimeout != null) {
|
||||
this.gateway.setReplyTimeout(this.replyTimeout);
|
||||
}
|
||||
if (this.replyTimeout != null) {
|
||||
this.gateway.setReplyTimeout(this.replyTimeout);
|
||||
}
|
||||
|
||||
if (replyChannel != null) {
|
||||
this.gateway.setReplyChannel(replyChannel);
|
||||
@@ -262,25 +277,29 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
if (this.sourceEvaluationContext == null) {
|
||||
this.sourceEvaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
|
||||
this.sourceEvaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
|
||||
}
|
||||
|
||||
StandardEvaluationContext targetContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
|
||||
StandardEvaluationContext targetContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
|
||||
// bean resolution is NOT allowed for the target of the enrichment
|
||||
targetContext.setBeanResolver(null);
|
||||
this.targetEvaluationContext = targetContext;
|
||||
|
||||
if (this.getBeanFactory() != null) {
|
||||
for (HeaderValueMessageProcessor<?> headerValueMessageProcessor : headerExpressions.values()) {
|
||||
if (headerValueMessageProcessor instanceof BeanFactoryAware) {
|
||||
((BeanFactoryAware) headerValueMessageProcessor).setBeanFactory(this.getBeanFactory());
|
||||
}
|
||||
for (HeaderValueMessageProcessor<?> headerValueMessageProcessor : this.headerExpressions.values()) {
|
||||
if (headerValueMessageProcessor instanceof BeanFactoryAware) {
|
||||
((BeanFactoryAware) headerValueMessageProcessor).setBeanFactory(getBeanFactory());
|
||||
}
|
||||
}
|
||||
for (HeaderValueMessageProcessor<?> headerValueMessageProcessor : this.nullResultHeaderExpressions.values()) {
|
||||
if (headerValueMessageProcessor instanceof BeanFactoryAware) {
|
||||
((BeanFactoryAware) headerValueMessageProcessor).setBeanFactory(getBeanFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
final Object requestPayload = requestMessage.getPayload();
|
||||
@@ -302,7 +321,8 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
actualRequestMessage = requestMessage;
|
||||
}
|
||||
else {
|
||||
final Object requestMessagePayload = this.requestPayloadExpression.getValue(this.sourceEvaluationContext, requestMessage);
|
||||
final Object requestMessagePayload =
|
||||
this.requestPayloadExpression.getValue(this.sourceEvaluationContext, requestMessage);
|
||||
actualRequestMessage = this.getMessageBuilderFactory().withPayload(requestMessagePayload)
|
||||
.copyHeaders(requestMessage.getHeaders()).build();
|
||||
}
|
||||
@@ -313,7 +333,35 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
else {
|
||||
replyMessage = this.gateway.sendAndReceiveMessage(actualRequestMessage);
|
||||
if (replyMessage == null) {
|
||||
return replyMessage;
|
||||
if (this.nullResultPropertyExpressions.isEmpty() && this.nullResultHeaderExpressions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<Expression, Expression> entry : this.nullResultPropertyExpressions.entrySet()) {
|
||||
Expression propertyExpression = entry.getKey();
|
||||
Expression valueExpression = entry.getValue();
|
||||
Object value = valueExpression.getValue(this.sourceEvaluationContext, requestMessage);
|
||||
propertyExpression.setValue(this.targetEvaluationContext, targetPayload, value);
|
||||
}
|
||||
if (this.nullResultHeaderExpressions.isEmpty()) {
|
||||
return targetPayload;
|
||||
}
|
||||
else {
|
||||
Map<String, Object> targetHeaders = new HashMap<String, Object>(
|
||||
this.nullResultHeaderExpressions.size());
|
||||
for (Map.Entry<String, HeaderValueMessageProcessor<?>> entry : this.nullResultHeaderExpressions
|
||||
.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(requestMessage);
|
||||
targetHeaders.put(header, value);
|
||||
}
|
||||
}
|
||||
return this.getMessageBuilderFactory().withPayload(targetPayload).copyHeaders(targetHeaders)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Map.Entry<Expression, Expression> entry : this.propertyExpressions.entrySet()) {
|
||||
@@ -343,8 +391,8 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle implementation. If no requestChannel is defined, this method
|
||||
* has no effect as in that case no Gateway is initialized.
|
||||
* Lifecycle implementation. If no requestChannel is defined, this method has no
|
||||
* effect as in that case no Gateway is initialized.
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
@@ -354,8 +402,8 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle implementation. If no requestChannel is defined, this method
|
||||
* has no effect as in that case no Gateway is initialized.
|
||||
* Lifecycle implementation. If no requestChannel is defined, this method has no
|
||||
* effect as in that case no Gateway is initialized.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
@@ -365,22 +413,17 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle implementation. If no requestChannel is defined, this method
|
||||
* will return always return true as no Gateway is initialized.
|
||||
* Lifecycle implementation. If no requestChannel is defined, this method will return
|
||||
* always return true as no Gateway is initialized.
|
||||
*/
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
if (this.gateway != null) {
|
||||
return this.gateway.isRunning();
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
return this.gateway == null || this.gateway.isRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal gateway implementation for request/reply handling.
|
||||
* Simply exposes the sendAndReceiveMessage method.
|
||||
* Internal gateway implementation for request/reply handling. Simply exposes the
|
||||
* sendAndReceiveMessage method.
|
||||
*/
|
||||
private static final class Gateway extends MessagingGatewaySupport {
|
||||
|
||||
|
||||
@@ -1458,6 +1458,14 @@
|
||||
Sub-element type for the 'enricher' element.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:attribute name="null-result-expression" type="xsd:string" use="optional">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The expression to be evaluated, when the underlying enricher sub-flow returns null.
|
||||
The evaluation context root object is requestMessage.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
|
||||
@@ -34,19 +34,19 @@ 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.messaging.MessageHandlingException;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.integration.transformer.ContentEnricher;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -163,6 +163,8 @@ public class EnricherParserTests {
|
||||
assertEquals(Gender.MALE, headers.get("testBean"));
|
||||
assertEquals("foo", headers.get("sourceName"));
|
||||
assertEquals("test", headers.get("notOverwrite"));
|
||||
requests.unsubscribe(foo);
|
||||
adviceCalled--;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util.xsd">
|
||||
|
||||
<channel id="input"/>
|
||||
|
||||
<channel id="output">
|
||||
<queue />
|
||||
</channel>
|
||||
|
||||
<header-enricher input-channel="requests1" output-channel="requests">
|
||||
<header-channels-to-string />
|
||||
</header-enricher>
|
||||
|
||||
<channel id="requests"/>
|
||||
|
||||
<channel id="replies"/>
|
||||
|
||||
<enricher id="enricher" input-channel="input"
|
||||
request-channel="requests1" request-timeout="1234"
|
||||
reply-timeout="9876" reply-channel="replies"
|
||||
order="99" should-clone-payload="true" output-channel="output">
|
||||
<property name="name" expression="payload.sourceName" null-result-expression="'Could not determine the name'"/>
|
||||
<property name="age" value="42" null-result-expression="'11'"/>
|
||||
<property name="gender" value="#{testBean}"/>
|
||||
<property name="married" null-result-expression="'1'"/>
|
||||
|
||||
<header name="foo" value="bar" null-result-expression="'Could not determine the foo'"/>
|
||||
<header name="testBean" expression="@testBean" null-result-expression="'Could not determine the testBean'"/>
|
||||
<header name="sourceName" null-result-expression="'Could not determine the sourceName'"/>
|
||||
<header name="notOverwrite" expression="payload.sourceName" overwrite="false"
|
||||
null-result-expression="'Could not determine the notOverwrite'"/>
|
||||
|
||||
<request-handler-advice-chain>
|
||||
<beans:bean class="org.springframework.integration.config.xml.EnricherParserTests4$FooAdvice" />
|
||||
</request-handler-advice-chain>
|
||||
</enricher>
|
||||
|
||||
<util:constant id="testBean" static-field="org.springframework.integration.config.xml.EnricherParserTests4$Gender.MALE"/>
|
||||
|
||||
</beans:beans>
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Liujiong
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class EnricherParserTests4 {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
private static volatile int adviceCalled;
|
||||
|
||||
@Test
|
||||
public void nullResultIntegrationTest() {
|
||||
SubscribableChannel requests = context.getBean("requests", SubscribableChannel.class);
|
||||
|
||||
class NullFoo extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
NullFoo foo = new NullFoo();
|
||||
foo.setOutputChannel(context.getBean("replies", MessageChannel.class));
|
||||
requests.subscribe(foo);
|
||||
Target original = new Target();
|
||||
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();
|
||||
assertEquals("Could not determine the name", enriched.getName());
|
||||
assertEquals(11, enriched.getAge());
|
||||
assertEquals(null, enriched.getGender());
|
||||
assertTrue(enriched.isMarried());
|
||||
assertNotSame(original, enriched);
|
||||
assertEquals(1, adviceCalled);
|
||||
|
||||
MessageHeaders headers = reply.getHeaders();
|
||||
assertEquals("Could not determine the foo", headers.get("foo"));
|
||||
assertEquals("Could not determine the testBean", headers.get("testBean"));
|
||||
assertEquals("Could not determine the sourceName", headers.get("sourceName"));
|
||||
assertEquals("test", headers.get("notOverwrite"));
|
||||
adviceCalled--;
|
||||
requests.unsubscribe(foo);
|
||||
}
|
||||
|
||||
|
||||
public static class Target implements Cloneable {
|
||||
|
||||
private volatile String name;
|
||||
|
||||
private volatile int age;
|
||||
|
||||
private volatile Gender gender;
|
||||
|
||||
private volatile boolean married;
|
||||
|
||||
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 Gender getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(Gender gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public boolean isMarried() {
|
||||
return married;
|
||||
}
|
||||
|
||||
public void setMarried(boolean married) {
|
||||
this.married = married;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
Target copy = new Target();
|
||||
copy.setName(this.name);
|
||||
copy.setAge(this.age);
|
||||
copy.setGender(this.gender);
|
||||
copy.setMarried(this.married);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Gender {
|
||||
MALE, FEMALE
|
||||
}
|
||||
|
||||
public static class FooAdvice extends AbstractRequestHandlerAdvice {
|
||||
|
||||
@Override
|
||||
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
|
||||
adviceCalled++;
|
||||
return callback.execute();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -653,7 +653,12 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
session.read(remoteFilePath, outputStream);
|
||||
}
|
||||
catch (Exception e) {
|
||||
/* Some operation systems acquire exclusive file-lock during file processing
|
||||
and the file can't be deleted without closing streams before.
|
||||
*/
|
||||
outputStream.close();
|
||||
tempFile.delete();
|
||||
|
||||
if (e instanceof RuntimeException){
|
||||
throw (RuntimeException) e;
|
||||
}
|
||||
|
||||
@@ -273,10 +273,10 @@
|
||||
send-timeout="" ]]><co id="payload-enricher08-co" linkends="payload-enricher08" /><![CDATA[
|
||||
should-clone-payload="false"> ]]><co id="payload-enricher09-co" linkends="payload-enricher09" /><![CDATA[
|
||||
<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="23" type="java.lang.Integer"/>
|
||||
<int:header name="" expression=""/> ]]><co id="payload-enricher12-co" linkends="payload-enricher12" /><![CDATA[
|
||||
<int:header name="" value="" overwrite="" type=""/>
|
||||
<int:property name="" expression="" null-result-expression="'Could not determine the name'"/> ]]><co id="payload-enricher11-co" linkends="payload-enricher11" /><![CDATA[
|
||||
<int:property name="" value="23" type="java.lang.Integer" null-result-expression="'0'"/>
|
||||
<int:header name="" expression="" null-result-expression=""/> ]]><co id="payload-enricher12-co" linkends="payload-enricher12" /><![CDATA[
|
||||
<int:header name="" value="" overwrite="" type="" null-result-expression=""/>
|
||||
</int:enricher>]]></programlisting>
|
||||
|
||||
<para>
|
||||
@@ -408,7 +408,7 @@
|
||||
application context (using the '@<beanName>.<beanProperty>'
|
||||
SpEL syntax).
|
||||
</para>
|
||||
<para>
|
||||
<para>
|
||||
Starting with <emphasis>4.0</emphasis>, when specifying
|
||||
a <code>value</code> attribute, you can also specify an optional
|
||||
<code>type</code> attribute. When the destination is a
|
||||
@@ -420,7 +420,12 @@
|
||||
<code>type</code> attribute allows you to, say, convert
|
||||
a String containing a number to an <classname>Integer</classname>
|
||||
value in the target payload.
|
||||
</para>
|
||||
</para>
|
||||
<para>
|
||||
Starting with <emphasis>4.1</emphasis>, you can also specify an optional
|
||||
<code>null-result-expression</code> attribute. When the <code>enricher</code>
|
||||
returns null, it will be evaluated and the output of the evaluation will be returned instead.
|
||||
</para>
|
||||
</callout>
|
||||
<callout arearefs="payload-enricher12-co" id="payload-enricher12">
|
||||
<para>
|
||||
@@ -435,12 +440,17 @@
|
||||
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.
|
||||
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>
|
||||
<para>
|
||||
Starting with <emphasis>4.1</emphasis>, you can also specify an optional
|
||||
<code>null-result-expression</code> attribute. When the <code>enricher</code>
|
||||
returns null, it will be evaluated and the output of the evaluation will be returned instead.
|
||||
</para>
|
||||
</callout>
|
||||
</calloutlist>
|
||||
|
||||
@@ -89,5 +89,13 @@
|
||||
See <xref linkend="aggregator-config"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
<section id="4.1-content-enricher-improvement">
|
||||
<title>Content Enricher Improvements</title>
|
||||
<para>
|
||||
Add null-result-expression attribute, which will be evaluated and returned if <code><enricher></code> returns null.
|
||||
It can be added in <code><header></code> and <code><property></code>.
|
||||
See <xref linkend="content-enricher"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user