From 7517963be2a8efdd9eae512bc95ef0b850ce0e42 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 16 Sep 2013 18:51:36 +0300 Subject: [PATCH] INT-3027: Headers Enrichment for * Add `
` sub-element to `` * Refactoring of `EnricherParser` * Add Headers Enrichment logic to `ContentEnricher` * Add tests * What's new and Content Enricher's `
` note JIRA: https://jira.springsource.org/browse/INT-3027 INT-3027: Addressing PR comments INT-3027: add NPE asserts to map properties INT-3027: add overwrite & type to enricher header Move `HeaderValueMessageProcessor` hierarchy to 'transformer.support' package INT-3027: Make header's overwrite=true by default Doc Polishing --- .../config/xml/EnricherParser.java | 59 +++++----- .../xml/HeaderEnricherParserSupport.java | 14 +-- .../transformer/ContentEnricher.java | 56 +++++++-- .../transformer/HeaderEnricher.java | 108 +----------------- .../AbstractHeaderValueMessageProcessor.java | 37 ++++++ ...EvaluatingHeaderValueMessageProcessor.java | 68 +++++++++++ .../support/HeaderValueMessageProcessor.java | 30 +++++ ...ProcessingHeaderValueMessageProcessor.java | 48 ++++++++ .../StaticHeaderValueMessageProcessor.java | 37 ++++++ .../transformer/support/package-info.java | 7 ++ .../config/xml/spring-integration-3.0.xsd | 36 ++++++ .../xml/EnricherParserTests-context.xml | 13 ++- .../config/xml/EnricherParserTests.java | 35 +++++- .../config/GroovyHeaderEnricherTests.java | 8 +- .../xml/transformer/XPathHeaderEnricher.java | 5 +- src/reference/docbook/content-enrichment.xml | 26 +++++ src/reference/docbook/whats-new.xml | 8 ++ 17 files changed, 433 insertions(+), 162 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/transformer/support/AbstractHeaderValueMessageProcessor.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/transformer/support/ExpressionEvaluatingHeaderValueMessageProcessor.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/transformer/support/HeaderValueMessageProcessor.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/transformer/support/MessageProcessingHeaderValueMessageProcessor.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/transformer/support/StaticHeaderValueMessageProcessor.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/transformer/support/package-info.java 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 6bec54fa9f..caaa76d902 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,10 @@ import java.util.List; import org.w3c.dom.Element; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.transformer.ContentEnricher; import org.springframework.util.CollectionUtils; @@ -34,6 +34,7 @@ import org.springframework.util.xml.DomUtils; * Parser for the 'enricher' element. * * @author Mark Fisher + * @author Artem Bilan * @since 2.1 */ public class EnricherParser extends AbstractConsumerEndpointParser { @@ -47,34 +48,34 @@ public class EnricherParser extends AbstractConsumerEndpointParser { IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply"); - - List propertyElements = DomUtils.getChildElementsByTagName(element, "property"); - if (!CollectionUtils.isEmpty(propertyElements)) { - ManagedMap propertyExpressions = new ManagedMap(); - for (Element propertyElement : propertyElements) { - String name = propertyElement.getAttribute("name"); - String value = propertyElement.getAttribute("value"); - String expression = propertyElement.getAttribute("expression"); - if (StringUtils.hasText(value) && StringUtils.hasText(expression)) { - parserContext.getReaderContext().error("The 'value' and 'expression' attributes are mutually exclusive on " + - "an element's sub-element.", parserContext.extractSource(propertyElement)); - } - if (StringUtils.hasText(value)) { - BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(LiteralExpression.class); - expressionBuilder.addConstructorArgValue(value); - propertyExpressions.put(name, expressionBuilder.getBeanDefinition()); - } - else if (StringUtils.hasText(expression)) { - BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class); - expressionBuilder.addConstructorArgValue(expression); - propertyExpressions.put(name, expressionBuilder.getBeanDefinition()); - } - else { - parserContext.getReaderContext().error("Exactly one of 'value' or 'expression' attributes must be provided on " + - "an element's sub-element.", parserContext.extractSource(propertyElement)); - } + + List subElements = DomUtils.getChildElementsByTagName(element, "property"); + if (!CollectionUtils.isEmpty(subElements)) { + ManagedMap expressions = new ManagedMap(); + for (Element subElement : subElements) { + String name = subElement.getAttribute("name"); + BeanDefinition beanDefinition = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("value", + "expression", parserContext, subElement, true); + expressions.put(name, beanDefinition); } - builder.addPropertyValue("propertyExpressions", propertyExpressions); + builder.addPropertyValue("propertyExpressions", expressions); + } + + subElements = DomUtils.getChildElementsByTagName(element, "header"); + if (!CollectionUtils.isEmpty(subElements)) { + ManagedMap expressions = new ManagedMap(); + for (Element subElement : subElements) { + String name = subElement.getAttribute("name"); + BeanDefinition expressionDefinition = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("value", + "expression", parserContext, subElement, true); + BeanDefinitionBuilder valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( + IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor"); + valueProcessorBuilder.addConstructorArgValue(expressionDefinition) + .addConstructorArgValue(subElement.getAttribute("type")); + IntegrationNamespaceUtils.setValueIfAttributeDefined(valueProcessorBuilder, subElement, "overwrite"); + expressions.put(name, valueProcessorBuilder.getBeanDefinition()); + } + builder.addPropertyValue("headerExpressions", expressions); } IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "should-clone-payload"); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java index bd525a1dce..5dba32f685 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -179,7 +179,7 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar Object headerValue = (headerType != null) ? new TypedStringValue(value, headerType) : value; valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$StaticHeaderValueMessageProcessor"); + IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor"); valueProcessorBuilder.addConstructorArgValue(headerValue); } else if (isExpression) { @@ -188,7 +188,7 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar "The 'method' attribute cannot be used with the 'expression' attribute.", element); } valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$ExpressionEvaluatingHeaderValueMessageProcessor"); + IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor"); if (expressionElement != null) { BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DynamicExpression.class); dynamicExpressionBuilder.addConstructorArgValue(expressionElement.getAttribute("key")); @@ -207,7 +207,7 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar } if (hasMethod || isScript) { valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$MessageProcessingHeaderValueMessageProcessor"); + IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor"); valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition); if (hasMethod) { valueProcessorBuilder.addConstructorArgValue(method); @@ -215,7 +215,7 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar } else { valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$StaticHeaderValueMessageProcessor"); + IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor"); valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition); } } @@ -226,13 +226,13 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar } if (hasMethod) { valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$MessageProcessingHeaderValueMessageProcessor"); + IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor"); valueProcessorBuilder.addConstructorArgReference(ref); valueProcessorBuilder.addConstructorArgValue(method); } else { valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$StaticHeaderValueMessageProcessor"); + IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor"); valueProcessorBuilder.addConstructorArgReference(ref); } } 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 fa81fbb07d..51800b2cca 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 @@ -34,6 +34,7 @@ import org.springframework.integration.expression.IntegrationEvaluationContextAw import org.springframework.integration.gateway.MessagingGatewaySupport; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.transformer.support.HeaderValueMessageProcessor; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -51,7 +52,9 @@ import org.springframework.util.ReflectionUtils; */ public class ContentEnricher extends AbstractReplyProducingMessageHandler implements Lifecycle, IntegrationEvaluationContextAware { - private final Map propertyExpressions = new HashMap(); + private volatile Map propertyExpressions = new HashMap(); + + private volatile Map> headerExpressions = new HashMap>(); private final SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); @@ -80,16 +83,28 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem */ public void setPropertyExpressions(Map propertyExpressions) { Assert.notEmpty(propertyExpressions, "propertyExpressions must not be empty"); - synchronized (this.propertyExpressions) { - this.propertyExpressions.clear(); - for (Map.Entry entry : propertyExpressions.entrySet()) { - String key = entry.getKey(); - Expression value = entry.getValue(); - Assert.notNull(key, "propertyExpressions key must not be null"); - Assert.notNull(value, "propertyExpressions value must not be null"); - this.propertyExpressions.put(parser.parseExpression(key), value); - } + Assert.noNullElements(propertyExpressions.keySet().toArray(), "propertyExpressions keys must not be empty"); + Assert.noNullElements(propertyExpressions.values().toArray(), "propertyExpressions values must not be empty"); + Map localMap = new HashMap(propertyExpressions.size()); + for (Map.Entry entry : propertyExpressions.entrySet()) { + String key = entry.getKey(); + Expression value = entry.getValue(); + localMap.put(parser.parseExpression(key), value); } + this.propertyExpressions = localMap; + } + + /** + * Provide the map of {@link HeaderValueMessageProcessor} to evaluate when enriching + * the target MessageHeaders. + * The keys should simply be header names, and the values should be Expressions + * that will evaluate against the reply Message as the root object. + */ + public void setHeaderExpressions(Map> headerExpressions) { + Assert.notEmpty(headerExpressions, "headerExpressions must not be empty"); + Assert.noNullElements(headerExpressions.keySet().toArray(), "headerExpressions keys must not be empty"); + Assert.noNullElements(headerExpressions.values().toArray(), "headerExpressions values must not be empty"); + this.headerExpressions = new HashMap>(headerExpressions); } /** @@ -221,7 +236,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem final Object targetPayload; if (requestPayload instanceof Cloneable && this.shouldClonePayload) { try { - Method cloneMethod = requestPayload.getClass().getMethod("clone", new Class[0]); + Method cloneMethod = requestPayload.getClass().getMethod("clone"); targetPayload = ReflectionUtils.invokeMethod(cloneMethod, requestPayload); } catch (Exception e) { @@ -256,7 +271,24 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem Object value = valueExpression.getValue(this.sourceEvaluationContext, replyMessage); propertyExpression.setValue(this.targetEvaluationContext, targetPayload, value); } - return targetPayload; + + if (this.headerExpressions.isEmpty()) { + return targetPayload; + } + else { + Map targetHeaders = new HashMap(this.headerExpressions.size()); + for (Map.Entry> entry : this.headerExpressions.entrySet()) { + String header = entry.getKey(); + HeaderValueMessageProcessor valueProcessor = entry.getValue(); + Boolean overwrite = valueProcessor.isOverwrite(); + overwrite = overwrite != null ? overwrite : true; + if (overwrite || !requestMessage.getHeaders().containsKey(header)) { + Object value = valueProcessor.processMessage(replyMessage); + targetHeaders.put(header, value); + } + } + return MessageBuilder.withPayload(targetPayload).copyHeaders(targetHeaders).build(); + } } /** diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java index e0b337421e..9d57dff59e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,20 +21,14 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; + import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.SpelParserConfiguration; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.Message; import org.springframework.integration.MessagingException; -import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; import org.springframework.integration.handler.MessageProcessor; -import org.springframework.integration.handler.MethodInvokingMessageProcessor; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.transformer.support.HeaderValueMessageProcessor; /** * A Transformer that adds statically configured header values to a Message. @@ -146,78 +140,6 @@ public class HeaderEnricher implements Transformer, BeanNameAware, InitializingB } } - public static interface HeaderValueMessageProcessor extends MessageProcessor { - - Boolean isOverwrite(); - - } - - static abstract class AbstractHeaderValueMessageProcessor implements HeaderValueMessageProcessor { - - // null indicates no explicit setting; use header-enricher's - // 'default-overwrite' value - private volatile Boolean overwrite = null; - - public void setOverwrite(Boolean overwrite) { - this.overwrite = overwrite; - } - - public Boolean isOverwrite() { - return this.overwrite; - } - - } - - static class StaticHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor { - - private final T value; - - public StaticHeaderValueMessageProcessor(T value) { - this.value = value; - } - - public T processMessage(Message message) { - return this.value; - } - } - - static class ExpressionEvaluatingHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor - implements BeanFactoryAware { - - private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration( - true, true)); - - private final ExpressionEvaluatingMessageProcessor targetProcessor; - - /** - * Create a header value processor for the given Expression and the - * expected type of the expression evaluation result. The expectedType - * may be null if unknown. - */ - public ExpressionEvaluatingHeaderValueMessageProcessor(Expression expression, Class expectedType) { - this.targetProcessor = new ExpressionEvaluatingMessageProcessor(expression, expectedType); - } - - /** - * Create a header value processor for the given expression string and - * the expected type of the expression evaluation result. The - * expectedType may be null if unknown. - */ - public ExpressionEvaluatingHeaderValueMessageProcessor(String expressionString, Class expectedType) { - Expression expression = expressionParser.parseExpression(expressionString); - this.targetProcessor = new ExpressionEvaluatingMessageProcessor(expression, expectedType); - } - - public void setBeanFactory(BeanFactory beanFactory) { - this.targetProcessor.setBeanFactory(beanFactory); - } - - public T processMessage(Message message) { - return this.targetProcessor.processMessage(message); - } - - } - /* * (non-Javadoc) * @@ -241,7 +163,7 @@ public class HeaderEnricher implements Transformer, BeanNameAware, InitializingB for (HeaderValueMessageProcessor processor : this.headersToAdd.values()) { Boolean processerOverwrite = processor.isOverwrite(); if (processerOverwrite != null) { - shouldOverwrite |= processerOverwrite.booleanValue(); + shouldOverwrite |= processerOverwrite; } } if (!shouldOverwrite && !this.shouldSkipNulls) { @@ -250,26 +172,4 @@ public class HeaderEnricher implements Transformer, BeanNameAware, InitializingB } } - static class MessageProcessingHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor { - - private final MessageProcessor targetProcessor; - - public MessageProcessingHeaderValueMessageProcessor(MessageProcessor targetProcessor) { - this.targetProcessor = targetProcessor; - } - - public MessageProcessingHeaderValueMessageProcessor(Object targetObject) { - this(targetObject, null); - } - - public MessageProcessingHeaderValueMessageProcessor(Object targetObject, String method) { - this.targetProcessor = new MethodInvokingMessageProcessor(targetObject, method); - } - - public Object processMessage(Message message) { - return this.targetProcessor.processMessage(message); - } - - } - } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/AbstractHeaderValueMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/AbstractHeaderValueMessageProcessor.java new file mode 100644 index 0000000000..dc36a41ac9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/AbstractHeaderValueMessageProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.transformer.support; + +/** + * @author Mark Fisher + * @author Artem Bilan + * @since 3.0 + */ +abstract class AbstractHeaderValueMessageProcessor implements HeaderValueMessageProcessor { + + // null indicates no explicit setting + private volatile Boolean overwrite = null; + + public void setOverwrite(Boolean overwrite) { + this.overwrite = overwrite; + } + + public Boolean isOverwrite() { + return this.overwrite; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/ExpressionEvaluatingHeaderValueMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/ExpressionEvaluatingHeaderValueMessageProcessor.java new file mode 100644 index 0000000000..dbb0dd669c --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/ExpressionEvaluatingHeaderValueMessageProcessor.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.transformer.support; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.integration.Message; +import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; + +/** + * @author Mark Fisher + * @author Artem Bilan + * @since 3.0 + */ +class ExpressionEvaluatingHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor + implements BeanFactoryAware { + + private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration( + true, true)); + + private final ExpressionEvaluatingMessageProcessor targetProcessor; + + /** + * Create a header value processor for the given Expression and the + * expected type of the expression evaluation result. The expectedType + * may be null if unknown. + */ + public ExpressionEvaluatingHeaderValueMessageProcessor(Expression expression, Class expectedType) { + this.targetProcessor = new ExpressionEvaluatingMessageProcessor(expression, expectedType); + } + + /** + * Create a header value processor for the given expression string and + * the expected type of the expression evaluation result. The + * expectedType may be null if unknown. + */ + public ExpressionEvaluatingHeaderValueMessageProcessor(String expressionString, Class expectedType) { + Expression expression = expressionParser.parseExpression(expressionString); + this.targetProcessor = new ExpressionEvaluatingMessageProcessor(expression, expectedType); + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.targetProcessor.setBeanFactory(beanFactory); + } + + public T processMessage(Message message) { + return this.targetProcessor.processMessage(message); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/HeaderValueMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/HeaderValueMessageProcessor.java new file mode 100644 index 0000000000..282987296a --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/HeaderValueMessageProcessor.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.transformer.support; + +import org.springframework.integration.handler.MessageProcessor; + +/** + * @author Mark Fisher + * @author Artem Bilan + * @since 3.0 + */ +public interface HeaderValueMessageProcessor extends MessageProcessor { + + Boolean isOverwrite(); + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/MessageProcessingHeaderValueMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/MessageProcessingHeaderValueMessageProcessor.java new file mode 100644 index 0000000000..eebd1368bb --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/MessageProcessingHeaderValueMessageProcessor.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.transformer.support; + +import org.springframework.integration.Message; +import org.springframework.integration.handler.MessageProcessor; +import org.springframework.integration.handler.MethodInvokingMessageProcessor; + +/** + * @author Mark Fisher + * @author Artem Bilan + * @since 3.0 + */ +class MessageProcessingHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor { + + private final MessageProcessor targetProcessor; + + public MessageProcessingHeaderValueMessageProcessor(MessageProcessor targetProcessor) { + this.targetProcessor = targetProcessor; + } + + public MessageProcessingHeaderValueMessageProcessor(Object targetObject) { + this(targetObject, null); + } + + public MessageProcessingHeaderValueMessageProcessor(Object targetObject, String method) { + this.targetProcessor = new MethodInvokingMessageProcessor(targetObject, method); + } + + public Object processMessage(Message message) { + return this.targetProcessor.processMessage(message); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/StaticHeaderValueMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/StaticHeaderValueMessageProcessor.java new file mode 100644 index 0000000000..c90fbc34cd --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/StaticHeaderValueMessageProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.transformer.support; + +import org.springframework.integration.Message; + +/** + * @author Mark Fisher + * @author Artem Bilan + * @since 3.0 + */ +class StaticHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor { + + private final T value; + + public StaticHeaderValueMessageProcessor(T value) { + this.value = value; + } + + public T processMessage(Message message) { + return this.value; + } +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/package-info.java new file mode 100644 index 0000000000..9cc9a755c0 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/support/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains support classes for Transformers. + * + * @since 3.0 + * + */ +package org.springframework.integration.transformer.support; diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd index 8d8eb42dca..7f469dec69 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd @@ -1229,6 +1229,42 @@ + + + + Each header sub-element provides the name of a message header (via the required 'name' attribute). + Exactly one of the 'value' or 'expression' attributes must be provided as well. + The former for a literal value to set, and the latter for a SpEL expression to be evaluated. + The root object of the evaluation context is the Message that was returned from the flow initiated + by this enricher. + + + + + + + + + Boolean value to indicate whether this header value should overwrite an + existing header value. Unlike the Header Enricher, this attribute is 'true' + by default, similar to the 'property' attribute. + + + + + + + + + + The fully qualified class name of the header value's expected type. + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml index 5b2a3f9a66..b0c558e552 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml @@ -15,19 +15,28 @@ - - + +
+
+
+
+ + +
+ + 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 e70ef0739e..18cb5ed44b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,23 +19,30 @@ package org.springframework.integration.config.xml; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.util.Map; +import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.expression.Expression; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.MessageHeaders; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.core.SubscribableChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.transformer.ContentEnricher; @@ -46,6 +53,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Mark Fisher * @author Gunnar Hillert * @author Gary Russell + * @author Artem Bilan * * @since 2.1 */ @@ -124,7 +132,10 @@ public class EnricherParserTests { } }); Target original = new Target(); - Message request = MessageBuilder.withPayload(original).build(); + Message request = MessageBuilder.withPayload(original) + .setHeader("sourceName", "test") + .setHeader("notOverwrite", "test") + .build(); context.getBean("input", MessageChannel.class).send(request); Message reply = context.getBean("output", PollableChannel.class).receive(0); Target enriched = (Target) reply.getPayload(); @@ -133,6 +144,26 @@ public class EnricherParserTests { assertEquals("male", enriched.getGender()); assertNotSame(original, enriched); assertEquals(1, adviceCalled); + + MessageHeaders headers = reply.getHeaders(); + assertEquals("bar", headers.get("foo")); + assertEquals("male", headers.get("testBean")); + assertEquals("foo", headers.get("sourceName")); + assertEquals("test", headers.get("notOverwrite")); + } + + @Test + public void testInt3027WrongHeaderType() { + MessageChannel input = context.getBean("input2", MessageChannel.class); + try { + input.send(new GenericMessage("test")); + } + catch (Exception e) { + assertThat(e, Matchers.instanceOf(MessageHandlingException.class)); + assertThat(e.getCause(), Matchers.instanceOf(TypeMismatchException.class)); + assertThat(e.getCause().getMessage(), + Matchers.startsWith("Failed to convert value of type 'java.util.Date' to required type 'int'")); + } } private static class Source { diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyHeaderEnricherTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyHeaderEnricherTests.java index 39655e82df..e6efa554c6 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyHeaderEnricherTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyHeaderEnricherTests.java @@ -32,7 +32,7 @@ import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.groovy.GroovyScriptExecutingMessageProcessor; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; -import org.springframework.integration.transformer.HeaderEnricher; +import org.springframework.integration.transformer.support.HeaderValueMessageProcessor; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -71,11 +71,11 @@ public class GroovyHeaderEnricherTests { @SuppressWarnings("unchecked") @Test public void inlineScript() throws Exception{ - Map> headers = + Map> headers = TestUtils.getPropertyValue(headerEnricherWithInlineGroovyScript, "handler.transformer.headersToAdd", Map.class); assertEquals(1, headers.size()); - HeaderEnricher.HeaderValueMessageProcessor headerValueMessageProcessor = headers.get("TEST_HEADER"); - assertThat(headerValueMessageProcessor.getClass().getName(), Matchers.containsString("HeaderEnricher$MessageProcessingHeaderValueMessageProcessor")); + HeaderValueMessageProcessor headerValueMessageProcessor = headers.get("TEST_HEADER"); + assertThat(headerValueMessageProcessor.getClass().getName(), Matchers.containsString("MessageProcessingHeaderValueMessageProcessor")); Object targetProcessor = TestUtils.getPropertyValue(headerValueMessageProcessor, "targetProcessor"); assertEquals(GroovyScriptExecutingMessageProcessor.class, targetProcessor.getClass()); diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathHeaderEnricher.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathHeaderEnricher.java index 3ab35118eb..5d6587a534 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathHeaderEnricher.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/transformer/XPathHeaderEnricher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.w3c.dom.Node; import org.springframework.integration.Message; import org.springframework.integration.transformer.HeaderEnricher; +import org.springframework.integration.transformer.support.HeaderValueMessageProcessor; import org.springframework.integration.xml.DefaultXmlPayloadConverter; import org.springframework.integration.xml.XmlPayloadConverter; import org.springframework.integration.xml.xpath.XPathEvaluationType; @@ -33,7 +34,7 @@ import org.springframework.xml.xpath.XPathExpressionFactory; * Transformer implementation that evaluates XPath expressions against the * message payload and inserts the result of the evaluation into a message * header. The header names will match the keys in the map of expressions. - * + * * @author Jonas Partner * @author Mark Fisher * @since 2.0 diff --git a/src/reference/docbook/content-enrichment.xml b/src/reference/docbook/content-enrichment.xml index c22df0b27c..6e8cf27f3d 100644 --- a/src/reference/docbook/content-enrichment.xml +++ b/src/reference/docbook/content-enrichment.xml @@ -32,6 +32,9 @@ Please go to the adapter specific sections of this reference manual to learn more about those adapters. + + For more information regarding expressions support, please see . +
@@ -227,6 +230,8 @@ ]]> ]]> + ]]> ]]> @@ -358,6 +363,27 @@ application context (using the '@<beanName>.<beanProperty>' SpEL syntax). + + + + Each header sub-element provides the + name of a Message header (via the mandatory name + attribute). Exactly one of the value + or expression attributes must be provided + as well. The former for a literal value to set, and the + latter for a SpEL expression to be evaluated. The root + object of the evaluation context is the Message that was + returned from the flow initiated by this enricher, the + input Message if there is no request channel, or the + application context (using the '@<beanName>.<beanProperty>' + SpEL syntax). + Note, similar to the <header-enricher>, the <enricher>'s + header element has type and overwrite attributes. + However, a difference is that, with the <enricher>, + the overwrite attribute is true by default, + to be consistent with <enricher>'s + <property> sub-element. + diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 5f433134db..a4fe0fd437 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -86,6 +86,14 @@ MessageSource; see .
+
+ Content Enricher: Headers Enrichment Support + + The Content Enricher now provides configuration for <header/> + sub-elements, to enrich the outbound Message with headers based on the reply Message from the underlying + message flow. For more information see . + +
Spring Expression Language (SpEL) Configuration