INT-1639: Add <spel-function> support
* add `<spel-function>` XSD element * add `SpelFunctionParser` * add `SpelFunctionRegistrar` to avoid introducing some confused 'Method'-bean * add `SpelFunctionRegistrar` collaboration with `IntegrationEvaluationContextFactoryBean` * some refactoring for `IntegrationEvaluationContextFactoryBean` * polishing some failed tests after this change JIRA: https://jira.springsource.org/browse/INT-1639 INT-1639: SpelFunctionParser use BeanClassLoader INT-1639: Inherit 'functions' from parent AC INT-1639: Document the <spel-function> INT-1639: 'NoSuchBeanDefinitionException' fix Polishing
This commit is contained in:
committed by
Gary Russell
parent
1705a70def
commit
93f61160e2
@@ -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.
|
||||
@@ -44,6 +44,8 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Dave Syer
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public class OutboundGatewayTests {
|
||||
@@ -75,7 +77,7 @@ public class OutboundGatewayTests {
|
||||
}).when(context).getBean(anyString());
|
||||
when(context.containsBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)).thenReturn(true);
|
||||
IntegrationEvaluationContextFactoryBean integrationEvaluationContextFactoryBean = new IntegrationEvaluationContextFactoryBean();
|
||||
integrationEvaluationContextFactoryBean.setApplicationContext(context);
|
||||
integrationEvaluationContextFactoryBean.setBeanFactory(context);
|
||||
integrationEvaluationContextFactoryBean.afterPropertiesSet();
|
||||
StandardEvaluationContext evalContext = integrationEvaluationContextFactoryBean.getObject();
|
||||
when(context.getBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME, StandardEvaluationContext.class))
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.config;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -28,8 +29,7 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
@@ -42,62 +42,56 @@ import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} to populate {@link StandardEvaluationContext} instances enhanced with:
|
||||
* <ul>
|
||||
* <li>
|
||||
* a {@link BeanFactoryResolver}.
|
||||
* </li>
|
||||
* <li>
|
||||
* a {@link TypeConverter} based on the {@link ConversionService} from the application context.
|
||||
* </li>
|
||||
* <li>
|
||||
* a set of provided {@link PropertyAccessor}s including a default {@link MapAccessor}.
|
||||
* </li>
|
||||
* <li>
|
||||
* a set of provided SpEL functions.
|
||||
* </li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* This factory returns a new instance for each reference singleton - {@link #isSingleton()}
|
||||
* returns false.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*/
|
||||
public class IntegrationEvaluationContextFactoryBean implements FactoryBean<StandardEvaluationContext>,
|
||||
ApplicationContextAware, BeanFactoryAware, InitializingBean {
|
||||
BeanFactoryAware, InitializingBean {
|
||||
|
||||
private volatile List<PropertyAccessor> propertyAccessors = new ArrayList<PropertyAccessor>();
|
||||
|
||||
private TypeConverter typeConverter = new StandardTypeConverter();
|
||||
|
||||
private volatile Map<String, Method> functions = new LinkedHashMap<String, Method>();
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private TypeConverter typeConverter = new StandardTypeConverter();
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private BeanResolver beanResolver;
|
||||
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.beanResolver = new BeanFactoryResolver(this.applicationContext != null ? this.applicationContext : this.beanFactory);
|
||||
this.loadDefaultPropertyAccessors(this.propertyAccessors);
|
||||
if (this.applicationContext != null) {
|
||||
ConversionService conversionService = IntegrationContextUtils.getConversionService(this.applicationContext);
|
||||
if (conversionService != null) {
|
||||
this.typeConverter = new StandardTypeConverter(conversionService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPropertyAccessors(PropertyAccessor... accessors) {
|
||||
Assert.noNullElements(accessors, "Cannot have null elements in accessors");
|
||||
List<PropertyAccessor> propertyAccessors = new ArrayList<PropertyAccessor>();
|
||||
loadDefaultPropertyAccessors(propertyAccessors);
|
||||
for (PropertyAccessor accessor : accessors) {
|
||||
propertyAccessors.add(accessor);
|
||||
}
|
||||
Collections.addAll(propertyAccessors, accessors);
|
||||
this.propertyAccessors = propertyAccessors;
|
||||
}
|
||||
|
||||
private void loadDefaultPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
|
||||
propertyAccessors.add(new MapAccessor());
|
||||
}
|
||||
|
||||
public void setFunctions(Map<String, Method> functionsArg) {
|
||||
Map<String, Method> functions = new LinkedHashMap<String, Method>();
|
||||
for (Entry<String, Method> function : functionsArg.entrySet()) {
|
||||
@@ -107,20 +101,60 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean<Stan
|
||||
this.functions = functions;
|
||||
}
|
||||
|
||||
public void addFunction(String name, Method method) {
|
||||
this.functions.put(name, method);
|
||||
}
|
||||
|
||||
public void addFunctions(Map<String, Method> functions) {
|
||||
this.functions.putAll(functions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (this.propertyAccessors.isEmpty()) {
|
||||
this.loadDefaultPropertyAccessors(this.propertyAccessors);
|
||||
}
|
||||
if (this.beanFactory != null) {
|
||||
this.beanResolver = new BeanFactoryResolver(this.beanFactory);
|
||||
ConversionService conversionService = IntegrationContextUtils.getConversionService(this.beanFactory);
|
||||
if (conversionService != null) {
|
||||
this.typeConverter = new StandardTypeConverter(conversionService);
|
||||
}
|
||||
try {
|
||||
SpelFunctionRegistrar functionRegistrar = this.beanFactory.getBean(SpelFunctionRegistrar.class);
|
||||
if (functionRegistrar != null) {
|
||||
this.addFunctions(functionRegistrar.getFunctions());
|
||||
}
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException e) {
|
||||
//Ignore it.
|
||||
//There is no <spel-function> components within application context.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StandardEvaluationContext getObject() throws Exception {
|
||||
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
|
||||
evaluationContext.setBeanResolver(this.beanResolver);
|
||||
evaluationContext.setTypeConverter(this.typeConverter);
|
||||
|
||||
for (PropertyAccessor propertyAccessor : this.propertyAccessors) {
|
||||
evaluationContext.addPropertyAccessor(propertyAccessor);
|
||||
}
|
||||
evaluationContext.setBeanResolver(this.beanResolver);
|
||||
evaluationContext.setTypeConverter(this.typeConverter);
|
||||
|
||||
for (Entry<String, Method> functionEntry : this.functions.entrySet()) {
|
||||
evaluationContext.registerFunction(functionEntry.getKey(), functionEntry.getValue());
|
||||
}
|
||||
|
||||
return evaluationContext;
|
||||
}
|
||||
|
||||
private void loadDefaultPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
|
||||
propertyAccessors.add(new MapAccessor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return StandardEvaluationContext.class;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Utility class that keeps track of a Map of SpEL functions in order to register
|
||||
* them with the "integrationEvaluationContext" upon initialization.
|
||||
* </p>
|
||||
* <p>
|
||||
* The {@link org.springframework.integration.config.xml.SpelFunctionParser}
|
||||
* doesn't register a bean for each <spel-function> within application context.
|
||||
* There is no automatic way to get 'functions' from a parent context
|
||||
* and this class provide a hook to get 'functions' from the parent context.
|
||||
* </p>
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
class SpelFunctionRegistrar implements ApplicationContextAware, InitializingBean {
|
||||
|
||||
private final Map<String, Method> functions;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
SpelFunctionRegistrar(Map<String, Method> functions) {
|
||||
this.functions = functions;
|
||||
}
|
||||
|
||||
Map<String, Method> getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
ApplicationContext parent = this.applicationContext.getParent();
|
||||
if (parent != null) {
|
||||
try {
|
||||
SpelFunctionRegistrar parentFunctionRegistrar = parent.getBean(SpelFunctionRegistrar.class);
|
||||
Map<String, Method> parentFunctions = parentFunctionRegistrar.getFunctions();
|
||||
for (String key : parentFunctions.keySet()) {
|
||||
if(!this.functions.containsKey(key)) {
|
||||
this.functions.put(key, parentFunctions.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException e) {
|
||||
//Ignore it.
|
||||
//There is no <spel-function> components within parent application context.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -24,6 +24,7 @@ package org.springframework.integration.config.xml;
|
||||
* @author Oleg Zhurakousky
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHandler {
|
||||
|
||||
@@ -74,6 +75,7 @@ public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHan
|
||||
registerBeanDefinitionParser("control-bus", new ControlBusParser());
|
||||
registerBeanDefinitionParser("wire-tap", new GlobalWireTapParser());
|
||||
registerBeanDefinitionParser("transaction-synchronization-factory", new TransactionSynchronizationFactoryParser());
|
||||
registerBeanDefinitionParser("spel-function", new SpelFunctionParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.config.xml;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Parser for the <spel-function> element.
|
||||
* Doesn't register a bean within application context, collects 'functions'
|
||||
* within {@link org.springframework.integration.config.SpelFunctionRegistrar} bean.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SpelFunctionParser implements BeanDefinitionParser {
|
||||
|
||||
private final Map<String, Method> functions = new LinkedHashMap<String, Method>();
|
||||
|
||||
private volatile boolean initialized;
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
|
||||
this.initializeSpelFunctionRegistrarIfNecessary(parserContext);
|
||||
|
||||
String id = element.getAttribute("id");
|
||||
String className = element.getAttribute("class");
|
||||
String signature = element.getAttribute("method");
|
||||
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = ClassUtils.forName(className, parserContext.getReaderContext().getBeanClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
parserContext.getReaderContext().error(e.getMessage(), element);
|
||||
}
|
||||
|
||||
Method method = BeanUtils.resolveSignature(signature, clazz);
|
||||
|
||||
if (method == null) {
|
||||
parserContext.getReaderContext().error(String.format("No declared method '%s' in class '%s'",
|
||||
signature, className), element);
|
||||
return null;
|
||||
}
|
||||
if (!Modifier.isStatic(method.getModifiers())) {
|
||||
parserContext.getReaderContext().error("SpEL-function method has to be 'static'", element);
|
||||
}
|
||||
|
||||
this.functions.put(id, method);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private synchronized void initializeSpelFunctionRegistrarIfNecessary(ParserContext parserContext) {
|
||||
if (!this.initialized) {
|
||||
BeanDefinitionBuilder registrarBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE + ".config.SpelFunctionRegistrar")
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
.addConstructorArgValue(this.functions);
|
||||
BeanDefinitionReaderUtils.registerWithGeneratedName(registrarBuilder.getBeanDefinition(),
|
||||
parserContext.getRegistry());
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3883,6 +3883,40 @@ The list of component name patterns you want to track (e.g., tracked-components
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="spel-function">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="id" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies the name of the SpEL function.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="class" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The fully qualified class name where to find the static method for SpEL function.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="direct">
|
||||
<tool:expected-type type="java.lang.Class"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="method" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The method signature in the form 'methodName[([arg_list])]',
|
||||
where 'arg_list' is an optional, comma-separated list of fully-qualified
|
||||
type names.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
<xsd:complexType name="control-bus-type">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
|
||||
@@ -17,4 +17,8 @@
|
||||
|
||||
<bean class="org.springframework.integration.expression.ParentContextTests$Foo" />
|
||||
|
||||
<int:spel-function id="bar" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
|
||||
|
||||
<int:spel-function id="barChild" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -19,4 +19,8 @@
|
||||
|
||||
<bean class="org.springframework.integration.expression.ParentContextTests$Foo" />
|
||||
|
||||
<int:spel-function id="bar" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
|
||||
|
||||
<int:spel-function id="barParent" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -19,14 +19,18 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
@@ -36,6 +40,7 @@ import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
@@ -51,17 +56,34 @@ public class ParentContextTests {
|
||||
* and parent contexts work.
|
||||
*/
|
||||
@Test
|
||||
public void testSpelBeanReferencesInChildAndParent() {
|
||||
ApplicationContext parent = new ClassPathXmlApplicationContext("ParentContext-context.xml", this.getClass());
|
||||
public void testSpelBeanReferencesInChildAndParent() throws ClassNotFoundException {
|
||||
//To check if 'org.springframework.integration.config.SpelFunctionRegistrar#afterPropertiesSet()'
|
||||
//doesn't throw any Exception
|
||||
AbstractApplicationContext superParent = new GenericApplicationContext();
|
||||
superParent.refresh();
|
||||
|
||||
AbstractApplicationContext parent = new ClassPathXmlApplicationContext(new String[]{"ParentContext-context.xml"},
|
||||
this.getClass(), superParent);
|
||||
|
||||
Class<?> spelFunctionRegistrarClass = Class.forName("org.springframework.integration.config.SpelFunctionRegistrar");
|
||||
Object parentSpelFunctionRegistrar = parent.getBean(spelFunctionRegistrarClass);
|
||||
assertEquals(2, TestUtils.getPropertyValue(parentSpelFunctionRegistrar, "functions", Map.class).size());
|
||||
|
||||
assertEquals(2, evalContexts.size());
|
||||
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(parent);
|
||||
child.setConfigLocation("org/springframework/integration/expression/ChildContext-context.xml");
|
||||
child.refresh();
|
||||
|
||||
Object childSpelFunctionRegistrar = child.getBean(spelFunctionRegistrarClass);
|
||||
Map functions = TestUtils.getPropertyValue(childSpelFunctionRegistrar, "functions", Map.class);
|
||||
assertEquals(3, functions.size());
|
||||
assertTrue(functions.containsKey("barParent"));
|
||||
|
||||
assertEquals(3, evalContexts.size());
|
||||
assertSame(evalContexts.get(0).getBeanResolver(), evalContexts.get(1).getBeanResolver());
|
||||
assertNotSame(evalContexts.get(1).getBeanResolver(), evalContexts.get(2).getBeanResolver());
|
||||
assertSame(parent, TestUtils.getPropertyValue(evalContexts.get(0).getBeanResolver(), "beanFactory"));
|
||||
assertSame(child, TestUtils.getPropertyValue(evalContexts.get(2).getBeanResolver(), "beanFactory"));
|
||||
assertSame(parent.getBeanFactory(), TestUtils.getPropertyValue(evalContexts.get(0).getBeanResolver(), "beanFactory"));
|
||||
assertSame(child.getBeanFactory(), TestUtils.getPropertyValue(evalContexts.get(2).getBeanResolver(), "beanFactory"));
|
||||
|
||||
// Test transformer expressions
|
||||
child.getBean("input", MessageChannel.class).send(new GenericMessage<String>("baz"));
|
||||
@@ -82,4 +104,12 @@ public class ParentContextTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Bar {
|
||||
|
||||
public static Object bar(Object o) {
|
||||
return o;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,16 @@
|
||||
|
||||
<transformer input-channel="beanResolvingInput" expression="@testBean.foo + payload.toUpperCase()" output-channel="output"/>
|
||||
|
||||
<transformer input-channel="spelFunctionInput" expression="#trim(payload)" output-channel="output"/>
|
||||
|
||||
<beans:bean id="testBean" class="org.springframework.integration.transformer.SpelTransformerIntegrationTests$TestBean"/>
|
||||
|
||||
<chain id="transformerChain" input-channel="transformerChainInput">
|
||||
<transformer expression="null"/>
|
||||
</chain>
|
||||
|
||||
<spel-function id="trim" class="org.springframework.util.StringUtils" method="trimWhitespace"/>
|
||||
|
||||
<beans:bean id="integrationEvaluationContext" class="org.springframework.integration.config.IntegrationEvaluationContextFactoryBean">
|
||||
<beans:property name="propertyAccessors">
|
||||
<util:list>
|
||||
@@ -36,8 +40,11 @@
|
||||
</beans:bean>
|
||||
|
||||
<channel id="fooin" />
|
||||
|
||||
<transformer id="foo" input-channel="fooin" expression="payload.bar" />
|
||||
|
||||
<channel id="functionIn" />
|
||||
|
||||
<transformer id="bar" input-channel="functionIn" expression="#bar(#root)" />
|
||||
|
||||
</beans:beans>
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.util.Assert;
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@@ -70,6 +71,9 @@ public class SpelTransformerIntegrationTests {
|
||||
@Autowired @Qualifier("bar.handler")
|
||||
private AbstractReplyProducingMessageHandler barHandler;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel spelFunctionInput;
|
||||
|
||||
|
||||
@Test
|
||||
public void simple() {
|
||||
@@ -119,6 +123,14 @@ public class SpelTransformerIntegrationTests {
|
||||
assertEquals("bar", reply.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt1639SpelFunction() {
|
||||
Message<?> message = MessageBuilder.withPayload(" foo ").build();
|
||||
this.spelFunctionInput.send(message);
|
||||
Message<?> result = output.receive(0);
|
||||
assertEquals("foo", result.getPayload());
|
||||
}
|
||||
|
||||
static class TestBean {
|
||||
|
||||
public String getFoo() {
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
properties (using getters and setters). This is how the <interfacename>Message</interfacename> headers
|
||||
and payload properties are accessible.
|
||||
</para>
|
||||
</section>
|
||||
<section id="spel-customization">
|
||||
<title>SpEL Evaluation Context Customization</title>
|
||||
<para>
|
||||
Starting with Spring Integration 3.0, it is possible to add additional
|
||||
<interfacename>PropertyAccessor</interfacename>s to the SpEL evaluation context.
|
||||
@@ -58,8 +61,7 @@
|
||||
</property>
|
||||
<property name="functions">
|
||||
<map>
|
||||
<entry key="barcalc"
|
||||
value="#{T(foo.MyFunctions).getMethod('calc', T(foo.MyBar))}"/>
|
||||
<entry key="barcalc" value="#{T(foo.MyFunctions).getMethod('calc', T(foo.MyBar))}"/>
|
||||
</map>
|
||||
</property>
|
||||
</bean>]]></programlisting>
|
||||
@@ -85,4 +87,63 @@
|
||||
<code>"#barcalc(payload.myBar)"</code>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="spel-functions">
|
||||
<title>SpEL Functions</title>
|
||||
<para>
|
||||
Namespace support is provided for easy addition of SpEL custom functions.
|
||||
You can specify <code><spel-function/></code> components to provide
|
||||
<ulink url="http://static.springsource.org/spring-framework/docs/current/spring-framework-reference/html/expressions.html#expressions-ref-functions">
|
||||
custom SpEL functions</ulink> to the <interfacename>EvaluationContext</interfacename> used throughout the framework.
|
||||
Instead of configuring the factory bean above, simply add one or more of these components
|
||||
and the framework will automatically add them to the default <emphasis>integrationEvaluationContext</emphasis>
|
||||
factory bean.
|
||||
</para>
|
||||
<para>For example, assuming we have a useful static method to evaluate XPath:</para>
|
||||
<programlisting language="xml"><![CDATA[<int:spel-function id="xpath"
|
||||
class="com.foo.test.XPathUtils" method="evaluate(java.lang.String, java.lang.Object)"/>
|
||||
|
||||
<int:transformer input-channel="in" output-channel="out"
|
||||
expression="#xpath('//foo/@bar', payload)" />
|
||||
]]></programlisting>
|
||||
<para>
|
||||
With this sample:
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
The default <classname>IntegrationEvaluationContextFactoryBean</classname> bean with id
|
||||
<emphasis>integrationEvaluationContext</emphasis> is registered with the application
|
||||
context.
|
||||
</listitem>
|
||||
<listitem>
|
||||
The <code><spel-function/></code> is parsed and added to the <code>functions</code>
|
||||
Map of <emphasis>integrationEvaluationContext</emphasis> as map entry with <code>id</code> as the key
|
||||
and the static <classname>Method</classname> as the value.
|
||||
</listitem>
|
||||
<listitem>
|
||||
The <emphasis>integrationEvaluationContext</emphasis> factory bean creates a new
|
||||
<classname>StandardEvaluationContext</classname> instance,
|
||||
and it is configured with the default
|
||||
<interfacename>PropertyAccessor</interfacename>s, <interfacename>BeanResolver</interfacename>
|
||||
and the custom function.
|
||||
</listitem>
|
||||
<listitem>
|
||||
That <interfacename>EvaluationContext</interfacename> instance is injected into the
|
||||
<classname>ExpressionEvaluatingTransformer</classname> bean.
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
<note>
|
||||
SpEL functions declared in a parent context are also made available in any child context(s). Each
|
||||
context has its own instance of the <emphasis>integrationEvaluationContext</emphasis> factory bean
|
||||
because each needs a different <interfacename>BeanResolver</interfacename>, but the function
|
||||
declarations are inherited (and can be overridden if needed by declaring a SpEL function with
|
||||
the same name. The functions themselves are processed by the framework - they do not appear
|
||||
as beans in the application context.
|
||||
</note>
|
||||
<note>
|
||||
At this time, <interfacename>PropertyAccessor</interfacename>s are not inherited and must be
|
||||
declared as described above in each context.
|
||||
</note>
|
||||
</para>
|
||||
</section>
|
||||
</appendix>
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
<title>JMX Support</title>
|
||||
<para>
|
||||
A new <code><int-jmx:tree-polling-channel-adapter/></code> is provided; this
|
||||
adapter queries the JMX MBean tree and sends a message with a payload that is the
|
||||
graph of objects that matches the query. By default the MBeans are mapped to
|
||||
primitives and simple Objects like Map, List and arrays - permitting simple
|
||||
adapter queries the JMX MBean tree and sends a message with a payload that is the
|
||||
graph of objects that matches the query. By default the MBeans are mapped to
|
||||
primitives and simple Objects like Map, List and arrays - permitting simple
|
||||
transformation, for example, to JSON
|
||||
<xref linkend="jmx"/>.
|
||||
</para>
|
||||
@@ -82,11 +82,19 @@
|
||||
<section id="3.0-spel-customization">
|
||||
<title>Spring Expression Language (SpEL) Configuration</title>
|
||||
<para>
|
||||
A new factory bean is provided to allow
|
||||
A new <classname>IntegrationEvaluationContextFactoryBean</classname> is provided to allow
|
||||
configuration of custom <interfacename>PropertyAccessor</interfacename>s and functions for
|
||||
use in SpEL expressions throughout the framework. For more information see <xref linkend="spel" />.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-spel-functions">
|
||||
<title>SpEL Functions Support</title>
|
||||
<para>
|
||||
To customize the SpEL <interfacename>EvaluationContext</interfacename> with static
|
||||
<classname>Method</classname> functions the new <code><spel-function/></code>
|
||||
component is introduced. For more information see <xref linkend="spel-functions" />.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="3.0-general">
|
||||
@@ -303,7 +311,7 @@
|
||||
<para>
|
||||
All Outbound Gateways (e.g. <code><jdbc:outbound-gateway/></code> or <code><jms:outbound-gateway/></code>)
|
||||
are designed for 'request-reply' scenarios. A response is expected from the external service and
|
||||
will be published to the <code>reply-channel</code>, or the <code>replyChannel</code> message header.
|
||||
will be published to the <code>reply-channel</code>, or the <code>replyChannel</code> message header.
|
||||
However, there are some cases where the external system might not always return a
|
||||
result, e.g. a <code><jdbc:outbound-gateway/></code>, when a SELECT ends with an empty <interfacename>ResultSet</interfacename>
|
||||
or, say, a Web Service is One-Way. An option is therefore needed to configure whether or not a
|
||||
|
||||
Reference in New Issue
Block a user