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:
Liujiong
2014-07-23 19:49:42 +03:00
committed by Artem Bilan
parent 40f1122df2
commit 302edf9221
9 changed files with 453 additions and 119 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 '@&lt;beanName&gt;.&lt;beanProperty&gt;'
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 '@&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.
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>
<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>

View File

@@ -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>&lt;enricher&gt;</code> returns null.
It can be added in <code>&lt;header&gt;</code> and <code>&lt;property&gt;</code>.
See <xref linkend="content-enricher"/> for more information.
</para>
</section>
</section>
</chapter>