INT-2975: Add ValueExpression for Typed SpEL
JIRA: https://jira.spring.io/browse/INT-2975 INT-2975: Add `type` attr. for enricher property INT-2975: Check for `type`, polishing, docs Enricher: deprecate property expression type attr
This commit is contained in:
committed by
Gary Russell
parent
de123c1ee4
commit
466af8a2f5
@@ -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<String, Object> expressions = new ManagedMap<String, Object>();
|
||||
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 '<property>' of '<enricher>' " +
|
||||
"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<String, Object> expressions = new ManagedMap<String, Object>();
|
||||
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());
|
||||
|
||||
@@ -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 <V> - The expected value type.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
*/
|
||||
public class ValueExpression<V> implements Expression {
|
||||
|
||||
/** Fixed value of this expression */
|
||||
private final V value;
|
||||
|
||||
private final Class<V> aClass;
|
||||
|
||||
private final TypedValue typedResultValue;
|
||||
|
||||
private final TypeDescriptor typeDescriptor;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ValueExpression(V value) {
|
||||
Assert.notNull(value);
|
||||
this.value = value;
|
||||
this.aClass = (Class<V>) 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> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
|
||||
return getValue(desiredResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(Class<T> desiredResultType) throws EvaluationException {
|
||||
return org.springframework.expression.common.ExpressionUtils
|
||||
.convertTypedValue(null, this.typedResultValue, desiredResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType) throws EvaluationException {
|
||||
return getValue(context, desiredResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(EvaluationContext context, Class<T> desiredResultType) throws EvaluationException {
|
||||
return org.springframework.expression.common.ExpressionUtils
|
||||
.convertTypedValue(context, this.typedResultValue, desiredResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<V> getValueType() throws EvaluationException {
|
||||
return this.aClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<V> getValueType(Object rootObject) throws EvaluationException {
|
||||
return this.aClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<V> getValueType(EvaluationContext context) throws EvaluationException {
|
||||
return this.aClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<V> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1325,13 +1325,6 @@
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string" />
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="type" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:java.lang.Class">
|
||||
The fully qualified class name of the header value's expected type.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
@@ -1486,6 +1479,14 @@
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="type" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:java.lang.Class"><![CDATA[
|
||||
The fully qualified class name of the header value's expected type.
|
||||
Allowed only in case of 'value' attribute.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:element name="delayer">
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
<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/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/util
|
||||
http://www.springframework.org/schema/util/spring-util.xsd">
|
||||
|
||||
<channel id="input"/>
|
||||
|
||||
@@ -27,7 +30,8 @@
|
||||
order="99" should-clone-payload="true" output-channel="output">
|
||||
<property name="name" expression="payload.sourceName"/>
|
||||
<property name="age" value="42"/>
|
||||
<property name="gender" expression="@testBean"/>
|
||||
<property name="gender" value="#{testBean}"/>
|
||||
<property name="married" value="1" type="java.lang.Boolean"/>
|
||||
|
||||
<header name="foo" value="bar"/>
|
||||
<header name="testBean" expression="@testBean"/>
|
||||
@@ -43,8 +47,6 @@
|
||||
<header name="foo" expression="new java.util.Date()" type="int"/>
|
||||
</enricher>
|
||||
|
||||
<beans:bean id="testBean" class="java.lang.String">
|
||||
<beans:constructor-arg value="male"/>
|
||||
</beans:bean>
|
||||
<util:constant id="testBean" static-field="org.springframework.integration.config.xml.EnricherParserTests$Gender.MALE"/>
|
||||
|
||||
</beans:beans>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -274,8 +274,8 @@
|
||||
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=""/>
|
||||
<int:header name="" expression=""/> ]]><co id="payload-enricher12-co" linkends="payload-enricher12" /><![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:enricher>]]></programlisting>
|
||||
|
||||
@@ -408,6 +408,19 @@
|
||||
application context (using the '@<beanName>.<beanProperty>'
|
||||
SpEL syntax).
|
||||
</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
|
||||
typed setter method, the framework will coerce the value
|
||||
appropriately (as long as a <classname>PropertyEditor</classname>)
|
||||
exists to handle the conversion. If however, the target
|
||||
payload is a <interfacename>Map</interfacename> the
|
||||
entry will be populated with the value without conversion. The
|
||||
<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>
|
||||
</callout>
|
||||
<callout arearefs="payload-enricher12-co" id="payload-enricher12">
|
||||
<para>
|
||||
|
||||
Reference in New Issue
Block a user