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 c372b52d45..e0d111a48e 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 @@ -21,10 +21,12 @@ import java.util.List; import org.w3c.dom.Element; 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.xml.ParserContext; import org.springframework.integration.config.ExpressionFactoryBean; +import org.springframework.integration.expression.ValueExpression; import org.springframework.integration.transformer.ContentEnricher; import org.springframework.integration.transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor; import org.springframework.util.CollectionUtils; @@ -55,9 +57,48 @@ public class EnricherParser extends AbstractConsumerEndpointParser { 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); + + String value = subElement.getAttribute("value"); + String type = subElement.getAttribute("type"); + String expression = subElement.getAttribute("expression"); + + boolean hasAttributeValue = StringUtils.hasText(value); + boolean hasAttributeExpression = StringUtils.hasText(expression); + + 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); + } + + BeanDefinition expressionDef; + + if (hasAttributeValue) { + BeanDefinitionBuilder expressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ValueExpression.class); + if (StringUtils.hasText(type)) { + expressionBuilder.addConstructorArgValue(new TypedStringValue(value, type)); + } + else { + expressionBuilder.addConstructorArgValue(value); + } + expressionDef = expressionBuilder.getBeanDefinition(); + } + else { + if (StringUtils.hasText(type)) { + parserContext.getReaderContext().error("The 'type' attribute for '' of '' " + + "is not allowed with an 'expression' attribute.", element); + } + expressionDef = BeanDefinitionBuilder + .genericBeanDefinition(ExpressionFactoryBean.class) + .addConstructorArgValue(expression) + .getBeanDefinition(); + } + + + + expressions.put(name, expressionDef); } builder.addPropertyValue("propertyExpressions", expressions); } @@ -67,10 +108,18 @@ public class EnricherParser extends AbstractConsumerEndpointParser { 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(ExpressionEvaluatingHeaderValueMessageProcessor.class); - valueProcessorBuilder.addConstructorArgValue(expressionDefinition) + BeanDefinition expressionDefinition = IntegrationNamespaceUtils + .createExpressionDefinitionFromValueOrExpression("value", "expression", parserContext, + subElement, true); + 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); + } + BeanDefinitionBuilder valueProcessorBuilder = BeanDefinitionBuilder + .genericBeanDefinition(ExpressionEvaluatingHeaderValueMessageProcessor.class) + .addConstructorArgValue(expressionDefinition) .addConstructorArgValue(subElement.getAttribute("type")); IntegrationNamespaceUtils.setValueIfAttributeDefined(valueProcessorBuilder, subElement, "overwrite"); expressions.put(name, valueProcessorBuilder.getBeanDefinition()); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ValueExpression.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ValueExpression.java new file mode 100644 index 0000000000..a4f8a36872 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ValueExpression.java @@ -0,0 +1,172 @@ +/* + * Copyright 2014 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.expression; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.TypedValue; +import org.springframework.util.Assert; + +/** + * A very simple hardcoded implementation of the {@link Expression} interface that represents an + * immutable value. It is used as value holder in the context of expression evaluation. + * + * @param - The expected value type. + * + * @author Artem Bilan + * @since 4.0 + */ +public class ValueExpression implements Expression { + + /** Fixed value of this expression */ + private final V value; + + private final Class aClass; + + private final TypedValue typedResultValue; + + private final TypeDescriptor typeDescriptor; + + @SuppressWarnings("unchecked") + public ValueExpression(V value) { + Assert.notNull(value); + this.value = value; + this.aClass = (Class) this.value.getClass(); + this.typedResultValue = new TypedValue(this.value); + this.typeDescriptor = this.typedResultValue.getTypeDescriptor(); + } + + @Override + public V getValue() throws EvaluationException { + return this.value; + } + + @Override + public V getValue(Object rootObject) throws EvaluationException { + return this.value; + } + + @Override + public V getValue(EvaluationContext context) throws EvaluationException { + return this.value; + } + + @Override + public V getValue(EvaluationContext context, Object rootObject) throws EvaluationException { + return this.value; + } + + @Override + public T getValue(Object rootObject, Class desiredResultType) throws EvaluationException { + return getValue(desiredResultType); + } + + @Override + public T getValue(Class desiredResultType) throws EvaluationException { + return org.springframework.expression.common.ExpressionUtils + .convertTypedValue(null, this.typedResultValue, desiredResultType); + } + + @Override + public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { + return getValue(context, desiredResultType); + } + + @Override + public T getValue(EvaluationContext context, Class desiredResultType) throws EvaluationException { + return org.springframework.expression.common.ExpressionUtils + .convertTypedValue(context, this.typedResultValue, desiredResultType); + } + + @Override + public Class getValueType() throws EvaluationException { + return this.aClass; + } + + @Override + public Class getValueType(Object rootObject) throws EvaluationException { + return this.aClass; + } + + @Override + public Class getValueType(EvaluationContext context) throws EvaluationException { + return this.aClass; + } + + @Override + public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { + return this.aClass; + } + + @Override + public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { + return this.typeDescriptor; + } + + @Override + public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + return this.typeDescriptor; + } + + @Override + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { + return this.typeDescriptor; + } + + @Override + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { + return this.typeDescriptor; + } + + @Override + public boolean isWritable(EvaluationContext context) throws EvaluationException { + return false; + } + + @Override + public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { + return false; + } + + @Override + public boolean isWritable(Object rootObject) throws EvaluationException { + return false; + } + + @Override + public void setValue(EvaluationContext context, Object value) throws EvaluationException { + setValue(context, null, value); + } + + @Override + public void setValue(Object rootObject, Object value) throws EvaluationException { + setValue(null, rootObject, value); + } + + @Override + public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { + throw new EvaluationException(this.value.toString(), "Cannot call setValue() on a ValueExpression"); + } + + @Override + public String getExpressionString() { + return this.value.toString(); + } + +} diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.0.xsd index 0ffdc899a0..113d29e32b 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.0.xsd @@ -1325,13 +1325,6 @@ - - - - The fully qualified class name of the header value's expected type. - - - @@ -1486,6 +1479,14 @@ ]]> + + + + + 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 f62be84be9..73eb705c68 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 @@ -2,10 +2,13 @@ + http://www.springframework.org/schema/integration/spring-integration.xsd + http://www.springframework.org/schema/util + http://www.springframework.org/schema/util/spring-util.xsd"> @@ -27,7 +30,8 @@ order="99" should-clone-payload="true" output-channel="output"> - + +
@@ -43,8 +47,6 @@
- - - + 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 f7abf96b18..977a74ce4c 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 @@ -91,10 +91,14 @@ public class EnricherParserTests { assertEquals("42", e.getValue().getExpressionString()); } else if ("gender".equals(e.getKey().getExpressionString())) { - assertEquals("@testBean", e.getValue().getExpressionString()); + assertEquals(Gender.MALE.name(), e.getValue().getExpressionString()); + } + else if ("married".equals(e.getKey().getExpressionString())) { + assertEquals(Boolean.TRUE.toString(), e.getValue().getExpressionString()); } else { - throw new IllegalStateException("expected 'name', 'age', and 'gender' only, not: " + e.getKey().getExpressionString()); + throw new IllegalStateException("expected 'name', 'age', 'gender' and married only, not: " + + e.getKey().getExpressionString()); } } @@ -127,12 +131,15 @@ public class EnricherParserTests { @Test public void integrationTest() { SubscribableChannel requests = context.getBean("requests", SubscribableChannel.class); + class Foo extends AbstractReplyProducingMessageHandler { + @Override protected Object handleRequestMessage(Message requestMessage) { return new Source("foo"); } - }; + } + Foo foo = new Foo(); foo.setOutputChannel(context.getBean("replies", MessageChannel.class)); requests.subscribe(foo); @@ -146,13 +153,14 @@ public class EnricherParserTests { Target enriched = (Target) reply.getPayload(); assertEquals("foo", enriched.getName()); assertEquals(42, enriched.getAge()); - assertEquals("male", enriched.getGender()); + assertEquals(Gender.MALE, enriched.getGender()); + assertTrue(enriched.isMarried()); assertNotSame(original, enriched); assertEquals(1, adviceCalled); MessageHeaders headers = reply.getHeaders(); assertEquals("bar", headers.get("foo")); - assertEquals("male", headers.get("testBean")); + assertEquals(Gender.MALE, headers.get("testBean")); assertEquals("foo", headers.get("sourceName")); assertEquals("test", headers.get("notOverwrite")); } @@ -191,7 +199,9 @@ public class EnricherParserTests { private volatile int age; - private volatile String gender; + private volatile Gender gender; + + private volatile boolean married; public String getName() { return name; @@ -209,23 +219,37 @@ public class EnricherParserTests { this.age = age; } - public String getGender() { + public Gender getGender() { return gender; } - public void setGender(String 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 diff --git a/src/reference/docbook/content-enrichment.xml b/src/reference/docbook/content-enrichment.xml index 657a5600c2..6c68cf3ab7 100644 --- a/src/reference/docbook/content-enrichment.xml +++ b/src/reference/docbook/content-enrichment.xml @@ -274,8 +274,8 @@ should-clone-payload="false"> ]]> ]]> ]]> - ]]> + ]]> ]]> @@ -408,6 +408,19 @@ application context (using the '@<beanName>.<beanProperty>' SpEL syntax). + + Starting with 4.0, when specifying + a value attribute, you can also specify an optional + type attribute. When the destination is a + typed setter method, the framework will coerce the value + appropriately (as long as a PropertyEditor) + exists to handle the conversion. If however, the target + payload is a Map the + entry will be populated with the value without conversion. The + type attribute allows you to, say, convert + a String containing a number to an Integer + value in the target payload. +