From 93f61160e2088b21da8d6fbc9be468eb2a3f30af Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 27 May 2013 12:49:35 +0300 Subject: [PATCH] INT-1639: Add support * add `` 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 INT-1639: 'NoSuchBeanDefinitionException' fix Polishing --- .../amqp/config/OutboundGatewayTests.java | 6 +- ...tegrationEvaluationContextFactoryBean.java | 98 +++++++++++++------ .../config/SpelFunctionRegistrar.java | 83 ++++++++++++++++ .../xml/IntegrationNamespaceHandler.java | 4 +- .../config/xml/SpelFunctionParser.java | 93 ++++++++++++++++++ .../config/xml/spring-integration-3.0.xsd | 34 +++++++ .../expression/ChildContext-context.xml | 4 + .../expression/ParentContext-context.xml | 4 + .../expression/ParentContextTests.java | 38 ++++++- ...pelTransformerIntegrationTests-context.xml | 7 ++ .../SpelTransformerIntegrationTests.java | 12 +++ src/reference/docbook/spel.xml | 65 +++++++++++- src/reference/docbook/whats-new.xml | 18 +++- 13 files changed, 420 insertions(+), 46 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/SpelFunctionRegistrar.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/xml/SpelFunctionParser.java diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests.java index e2387a9b4c..68f34872fc 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests.java +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/OutboundGatewayTests.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. @@ -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)) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationEvaluationContextFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationEvaluationContextFactoryBean.java index d1f7b0b510..39a425b993 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationEvaluationContextFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationEvaluationContextFactoryBean.java @@ -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: + *
    + *
  • + * a {@link BeanFactoryResolver}. + *
  • + *
  • + * a {@link TypeConverter} based on the {@link ConversionService} from the application context. + *
  • + *
  • + * a set of provided {@link PropertyAccessor}s including a default {@link MapAccessor}. + *
  • + *
  • + * a set of provided SpEL functions. + *
  • + *
+ *

+ * 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, - ApplicationContextAware, BeanFactoryAware, InitializingBean { + BeanFactoryAware, InitializingBean { private volatile List propertyAccessors = new ArrayList(); - private TypeConverter typeConverter = new StandardTypeConverter(); - private volatile Map functions = new LinkedHashMap(); - 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 propertyAccessors = new ArrayList(); loadDefaultPropertyAccessors(propertyAccessors); - for (PropertyAccessor accessor : accessors) { - propertyAccessors.add(accessor); - } + Collections.addAll(propertyAccessors, accessors); this.propertyAccessors = propertyAccessors; } - private void loadDefaultPropertyAccessors(List propertyAccessors) { - propertyAccessors.add(new MapAccessor()); - } - public void setFunctions(Map functionsArg) { Map functions = new LinkedHashMap(); for (Entry function : functionsArg.entrySet()) { @@ -107,20 +101,60 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean 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 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 functionEntry : this.functions.entrySet()) { evaluationContext.registerFunction(functionEntry.getKey(), functionEntry.getValue()); } + return evaluationContext; } + private void loadDefaultPropertyAccessors(List propertyAccessors) { + propertyAccessors.add(new MapAccessor()); + } + @Override public Class getObjectType() { return StandardEvaluationContext.class; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/SpelFunctionRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SpelFunctionRegistrar.java new file mode 100644 index 0000000000..be98f649be --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/SpelFunctionRegistrar.java @@ -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; + +/** + *

+ * Utility class that keeps track of a Map of SpEL functions in order to register + * them with the "integrationEvaluationContext" upon initialization. + *

+ *

+ * 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. + *

+ * + * @author Artem Bilan + * + * @since 3.0 + */ +class SpelFunctionRegistrar implements ApplicationContextAware, InitializingBean { + + private final Map functions; + + private ApplicationContext applicationContext; + + SpelFunctionRegistrar(Map functions) { + this.functions = functions; + } + + Map 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 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 components within parent application context. + } + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java index 65574d2c66..cacddb2c69 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.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. @@ -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()); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/SpelFunctionParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/SpelFunctionParser.java new file mode 100644 index 0000000000..b734c1dc65 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/SpelFunctionParser.java @@ -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 functions = new LinkedHashMap(); + + 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; + } + } + +} 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 c02468e05b..bde0e093df 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 @@ -3883,6 +3883,40 @@ The list of component name patterns you want to track (e.g., tracked-components + + + + + + Specifies the name of the SpEL function. + + + + + + + The fully qualified class name where to find the static method for SpEL function. + + + + + + + + + + + + The method signature in the form 'methodName[([arg_list])]', + where 'arg_list' is an optional, comma-separated list of fully-qualified + type names. + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContext-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContext-context.xml index 8f0c3e9086..6eb178c80a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContext-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContext-context.xml @@ -19,4 +19,8 @@ + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContextTests.java b/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContextTests.java index 455496787c..1da8524b28 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContextTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/expression/ParentContextTests.java @@ -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("baz")); @@ -82,4 +104,12 @@ public class ParentContextTests { } } + + public static class Bar { + + public static Object bar(Object o) { + return o; + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests-context.xml index 04fba18278..1e75c00426 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests-context.xml @@ -15,12 +15,16 @@ + + + + @@ -36,8 +40,11 @@ + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests.java index 4c10f192b7..e753c1ab17 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests.java @@ -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() { diff --git a/src/reference/docbook/spel.xml b/src/reference/docbook/spel.xml index c3db2d4f42..809d444a8a 100644 --- a/src/reference/docbook/spel.xml +++ b/src/reference/docbook/spel.xml @@ -30,6 +30,9 @@ properties (using getters and setters). This is how the Message headers and payload properties are accessible. + +
+ SpEL Evaluation Context Customization Starting with Spring Integration 3.0, it is possible to add additional PropertyAccessors to the SpEL evaluation context. @@ -58,8 +61,7 @@ - + ]]> @@ -85,4 +87,63 @@ "#barcalc(payload.myBar)".
+
+ SpEL Functions + + Namespace support is provided for easy addition of SpEL custom functions. + You can specify <spel-function/> components to provide + + custom SpEL functions to the EvaluationContext 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 integrationEvaluationContext + factory bean. + + For example, assuming we have a useful static method to evaluate XPath: + + + + ]]> + + With this sample: + + + The default IntegrationEvaluationContextFactoryBean bean with id + integrationEvaluationContext is registered with the application + context. + + + The <spel-function/> is parsed and added to the functions + Map of integrationEvaluationContext as map entry with id as the key + and the static Method as the value. + + + The integrationEvaluationContext factory bean creates a new + StandardEvaluationContext instance, + and it is configured with the default + PropertyAccessors, BeanResolver + and the custom function. + + + That EvaluationContext instance is injected into the + ExpressionEvaluatingTransformer bean. + + + + + + SpEL functions declared in a parent context are also made available in any child context(s). Each + context has its own instance of the integrationEvaluationContext factory bean + because each needs a different BeanResolver, 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. + + + At this time, PropertyAccessors are not inherited and must be + declared as described above in each context. + + +
diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 800456cd6e..fa41e2cc1b 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -64,9 +64,9 @@ JMX Support A new <int-jmx:tree-polling-channel-adapter/> 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 . @@ -82,11 +82,19 @@
Spring Expression Language (SpEL) Configuration - A new factory bean is provided to allow + A new IntegrationEvaluationContextFactoryBean is provided to allow configuration of custom PropertyAccessors and functions for use in SpEL expressions throughout the framework. For more information see .
+
+ SpEL Functions Support + + To customize the SpEL EvaluationContext with static + Method functions the new <spel-function/> + component is introduced. For more information see . + +
@@ -303,7 +311,7 @@ All Outbound Gateways (e.g. <jdbc:outbound-gateway/> or <jms:outbound-gateway/>) are designed for 'request-reply' scenarios. A response is expected from the external service and - will be published to the reply-channel, or the replyChannel message header. + will be published to the reply-channel, or the replyChannel message header. However, there are some cases where the external system might not always return a result, e.g. a <jdbc:outbound-gateway/>, when a SELECT ends with an empty ResultSet or, say, a Web Service is One-Way. An option is therefore needed to configure whether or not a