INT-3133: Add <spel-property-accessors> Support

* Add <spel-property-accessors> to configure a list of beans
that implement `org.springframework.expression.PropertyAccessor`
* Add `SpelPropertyAccessorRegistrar` to manage a list of PropertyAccessors
for Integration infrastructure
* Refactoring for `SpelFunctionRegistrar` and `IntegrationEvaluationContextFactoryBean`
to fix the issue when there is no `SpelFunctionRegistrar`(`SpelPropertyAccessorRegistrar`)
in the child AC, but there is one in the parent.
Previously the inheritance did't work for that reason.
* Now parent/child logic moved to `IntegrationEvaluationContextFactoryBean`
* Add documentation

JIRA: https://jira.springsource.org/browse/INT-3133

INT-3133: Rebasing, polishing and documentation

INT-3133: PropertyAccessors override support

* `@Ignore` `SOLingerTests#finReceivedNioLinger()` test

INT-3133: Change JavaDoc link to 3.0.0.RC1

Polishing

- Remove duplicate check - last bean def wins
- Doc Polishing
This commit is contained in:
Artem Bilan
2013-09-06 10:49:53 +03:00
committed by Gary Russell
parent 00d9ba50ef
commit 19dd45a98a
16 changed files with 419 additions and 26 deletions

View File

@@ -29,6 +29,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -40,11 +41,13 @@ import org.springframework.integration.handler.MessageProcessor;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
/**
* @author Mark Fisher
* @author Dave Syer
* @author Gary Russell
* @author Artem Bilan
*
* @since 2.1
*/
@@ -76,6 +79,9 @@ public class OutboundGatewayTests {
}
}).when(context).getBean(anyString());
when(context.containsBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)).thenReturn(true);
when(context.getBean(ClassUtils.forName("org.springframework.integration.config.SpelPropertyAccessorRegistrar",
context.getClassLoader())))
.thenThrow(NoSuchBeanDefinitionException.class);
IntegrationEvaluationContextFactoryBean integrationEvaluationContextFactoryBean = new IntegrationEvaluationContextFactoryBean();
integrationEvaluationContextFactoryBean.setApplicationContext(context);
integrationEvaluationContextFactoryBean.afterPropertiesSet();

View File

@@ -28,6 +28,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
@@ -42,6 +43,7 @@ import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.util.Assert;
/**
* <p>
* {@link FactoryBean} to populate {@link StandardEvaluationContext} instances enhanced with:
* <ul>
* <li>
@@ -58,8 +60,14 @@ import org.springframework.util.Assert;
* </li>
* </ul>
* <p/>
* This factory returns a new instance for each reference singleton - {@link #isSingleton()}
* returns false.
* <p>
* After initialization this factory populates functions and property accessors from
* {@link SpelFunctionFactoryBean}s and {@link SpelPropertyAccessorRegistrar}, respectively.
* Functions and property accessors are also inherited from any parent context.
* </p>
* <p>
* This factory returns a new instance for each reference - {@link #isSingleton()} returns false.
* </p>
*
* @author Artem Bilan
* @author Gary Russell
@@ -75,7 +83,6 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean<Stan
private TypeConverter typeConverter = new StandardTypeConverter();
private ApplicationContext applicationContext;
private BeanResolver beanResolver;
@Override
@@ -120,6 +127,15 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean<Stan
this.functions.put(spelFunctionFactoryBean.getFunctionName(), spelFunctionFactoryBean.getObject());
}
}
try {
SpelPropertyAccessorRegistrar propertyAccessorRegistrar = this.applicationContext.getBean(SpelPropertyAccessorRegistrar.class);
this.propertyAccessors.addAll(propertyAccessorRegistrar.getPropertyAccessors());
}
catch (NoSuchBeanDefinitionException e) {
// There is no 'SpelPropertyAccessorRegistrar' bean with the parent application context
// Ignore it
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.util.Collection;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.expression.PropertyAccessor;
/**
* Utility class that keeps track of a Set of SpEL {@link PropertyAccessor}s
* in order to register them with the "integrationEvaluationContext" upon initialization.
*
* @author Artem Bilan
* @since 3.0
*/
class SpelPropertyAccessorRegistrar implements ApplicationContextAware, InitializingBean {
private final Map<String, PropertyAccessor> propertyAccessors;
private ApplicationContext applicationContext;
SpelPropertyAccessorRegistrar(Map<String, PropertyAccessor> propertyAccessors) {
this.propertyAccessors = propertyAccessors;
}
Collection<PropertyAccessor> getPropertyAccessors() {
return propertyAccessors.values();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
SpelPropertyAccessorRegistrar parentPropertyAccessorRegistrar = null;
try {
BeanFactory parentBeanFactory = this.applicationContext.getParentBeanFactory();
if (parentBeanFactory != null) {
parentPropertyAccessorRegistrar = parentBeanFactory.getBean(SpelPropertyAccessorRegistrar.class);
}
}
catch (NoSuchBeanDefinitionException e) {
// There is no 'SpelPropertyAccessorRegistrar' bean with the parent application context
// Ignore it
}
if (parentPropertyAccessorRegistrar != null) {
for (Map.Entry<String, PropertyAccessor> entry : parentPropertyAccessorRegistrar.propertyAccessors.entrySet()) {
if (!this.propertyAccessors.containsKey(entry.getKey())) {
this.propertyAccessors.put(entry.getKey(), entry.getValue());
}
}
}
}
}

View File

@@ -76,6 +76,7 @@ public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHan
registerBeanDefinitionParser("wire-tap", new GlobalWireTapParser());
registerBeanDefinitionParser("transaction-synchronization-factory", new TransactionSynchronizationFactoryParser());
registerBeanDefinitionParser("spel-function", new SpelFunctionParser());
registerBeanDefinitionParser("spel-property-accessors", new SpelPropertyAccessorsParser());
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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.util.Map;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Parser for the &lt;spel-property-accessors&gt; element.
*
* @author Artem Bilan
* @since 3.0
*/
public class SpelPropertyAccessorsParser implements BeanDefinitionParser {
private final Map<String, Object> propertyAccessors = new ManagedMap<String, Object>();
private volatile boolean initialized;
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
this.initializeSpelPropertyAccessorRegistrarIfNecessary(parserContext);
BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
String propertyAccessorName = null;
Object propertyAccessor = null;
if (node instanceof Element && !delegate.nodeNameEquals(node, BeanDefinitionParserDelegate.DESCRIPTION_ELEMENT)) {
Element ele = (Element) node;
if (delegate.nodeNameEquals(ele, BeanDefinitionParserDelegate.BEAN_ELEMENT)) {
propertyAccessorName = ele.getAttribute(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
if (!StringUtils.hasText(propertyAccessorName)) {
parserContext.getReaderContext()
.error("The '<bean>' 'id' attribute is required within 'spel-property-accessors'.", ele);
return null;
}
propertyAccessor = delegate.parseBeanDefinitionElement(ele);
}
else if (delegate.nodeNameEquals(ele, BeanDefinitionParserDelegate.REF_ELEMENT)) {
BeanReference propertyAccessorRef = (BeanReference) delegate.parsePropertySubElement(ele, null);
propertyAccessorName = propertyAccessorRef.getBeanName();
propertyAccessor = propertyAccessorRef;
}
else {
parserContext.getReaderContext().error("Only '<bean>' and '<ref>' elements are allowed.", element);
return null;
}
this.propertyAccessors.put(propertyAccessorName, propertyAccessor);
}
}
return null;
}
private synchronized void initializeSpelPropertyAccessorRegistrarIfNecessary(ParserContext parserContext) {
if (!this.initialized) {
BeanDefinitionBuilder registrarBuilder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE + ".config.SpelPropertyAccessorRegistrar")
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addConstructorArgValue(this.propertyAccessors);
BeanDefinitionReaderUtils.registerWithGeneratedName(registrarBuilder.getBeanDefinition(),
parserContext.getRegistry());
this.initialized = true;
}
}
}

View File

@@ -3959,6 +3959,21 @@ The list of component name patterns you want to track (e.g., tracked-components
</xsd:complexType>
</xsd:element>
<xsd:element name="spel-property-accessors">
<xsd:annotation>
<xsd:documentation>
Allows you to register PropertyAccessors (implementation of the PropertyAccessor interface) that will
be automatically registered with the SpEL EvaluationContext.
Note, the 'id' attribute of the bean definition below is required.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element ref="beans:bean" />
<xsd:element ref="beans:ref" />
</xsd:choice>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="control-bus-type">
<xsd:annotation>

View File

@@ -23,4 +23,9 @@
<int:spel-function id="jsonPath" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
<int:spel-property-accessors>
<ref bean="jsonPropertyAccessor"/>
</int:spel-property-accessors>
<bean id="jsonPropertyAccessor" class="org.springframework.integration.json.JsonPropertyAccessor"/>
</beans>

View File

@@ -23,4 +23,13 @@
<int:spel-function id="barParent" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
<int:spel-property-accessors>
<ref bean="parentJsonPropertyAccessor"/>
<ref bean="jsonPropertyAccessor"/>
</int:spel-property-accessors>
<bean id="jsonPropertyAccessor" class="org.springframework.integration.json.JsonPropertyAccessor"/>
<bean id="parentJsonPropertyAccessor" class="org.springframework.integration.json.JsonPropertyAccessor"/>
</beans>

View File

@@ -16,6 +16,7 @@
package org.springframework.integration.expression;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
@@ -27,13 +28,13 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
@@ -45,6 +46,7 @@ import org.springframework.integration.test.util.TestUtils;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 3.0
*
*/
@@ -57,37 +59,76 @@ public class ParentContextTests {
* BeanResolver. Verifies that the two Foos in the parent context get an evaluation context
* with the same bean resolver. Verifies that the one Foo in the child context gets a different
* bean resolver. Verifies that bean references in SpEL expressions to beans in the child
* and parent contexts work.
* and parent contexts work. Verifies that PropertyAccessors are inherited in the child context
* and the parent's ones are last in the propertyAccessors list of EvaluationContext.
* Verifies that SpEL functions are inherited from parent context and overridden with the same 'id'.
*
*/
@Test
@SuppressWarnings("unchecked")
public void testSpelBeanReferencesInChildAndParent() throws Exception {
AbstractApplicationContext parent = new ClassPathXmlApplicationContext("ParentContext-context.xml", this.getClass());
Object parentEvaluationContextFactoryBean = parent.getBean(IntegrationEvaluationContextFactoryBean.class);
Map parentFunctions = TestUtils.getPropertyValue(parentEvaluationContextFactoryBean, "functions", Map.class);
Map<?,?> parentFunctions = TestUtils.getPropertyValue(parentEvaluationContextFactoryBean, "functions", Map.class);
assertEquals(3, parentFunctions.size());
Object jsonPath = parentFunctions.get("jsonPath");
assertNotNull(jsonPath);
assertThat((Method) jsonPath, Matchers.isOneOf(JsonPathUtils.class.getMethods()));
assertEquals(2, evalContexts.size());
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(parent);
child.setConfigLocation("org/springframework/integration/expression/ChildContext-context.xml");
child.refresh();
Object childEvaluationContextFactoryBean = child.getBean(IntegrationEvaluationContextFactoryBean.class);
Map childFunctions = TestUtils.getPropertyValue(childEvaluationContextFactoryBean, "functions", Map.class);
Map<?,?> childFunctions = TestUtils.getPropertyValue(childEvaluationContextFactoryBean, "functions", Map.class);
assertEquals(4, childFunctions.size());
assertTrue(childFunctions.containsKey("barParent"));
jsonPath = childFunctions.get("jsonPath");
assertNotNull(jsonPath);
assertThat((Method) jsonPath, Matchers.not(Matchers.isOneOf(JsonPathUtils.class.getMethods())));
assertEquals(3, evalContexts.size());
assertSame(evalContexts.get(0).getBeanResolver(), evalContexts.get(1).getBeanResolver());
List<PropertyAccessor> propertyAccessors = evalContexts.get(0).getPropertyAccessors();
assertEquals(4, propertyAccessors.size());
PropertyAccessor parentPropertyAccessorOverride = parent.getBean("jsonPropertyAccessor", PropertyAccessor.class);
PropertyAccessor parentPropertyAccessor = parent.getBean("parentJsonPropertyAccessor", PropertyAccessor.class);
assertTrue(propertyAccessors.contains(parentPropertyAccessorOverride));
assertTrue(propertyAccessors.contains(parentPropertyAccessor));
assertTrue(propertyAccessors.indexOf(parentPropertyAccessorOverride) > propertyAccessors.indexOf(parentPropertyAccessor));
Map<String, Object> variables = (Map<String, Object>) TestUtils.getPropertyValue(evalContexts.get(0), "variables");
assertEquals(3, variables.size());
assertTrue(variables.containsKey("bar"));
assertTrue(variables.containsKey("barParent"));
assertTrue(variables.containsKey("jsonPath"));
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"));
propertyAccessors = evalContexts.get(1).getPropertyAccessors();
assertEquals(4, propertyAccessors.size());
assertTrue(propertyAccessors.contains(parentPropertyAccessorOverride));
variables = (Map<String, Object>) TestUtils.getPropertyValue(evalContexts.get(1), "variables");
assertEquals(3, variables.size());
assertTrue(variables.containsKey("bar"));
assertTrue(variables.containsKey("barParent"));
assertTrue(variables.containsKey("jsonPath"));
propertyAccessors = evalContexts.get(2).getPropertyAccessors();
assertEquals(4, propertyAccessors.size());
PropertyAccessor childPropertyAccessor = child.getBean("jsonPropertyAccessor", PropertyAccessor.class);
assertTrue(propertyAccessors.contains(childPropertyAccessor));
assertTrue(propertyAccessors.contains(parentPropertyAccessor));
assertFalse(propertyAccessors.contains(parentPropertyAccessorOverride));
assertTrue(propertyAccessors.indexOf(childPropertyAccessor) < propertyAccessors.indexOf(parentPropertyAccessor));
variables = (Map<String, Object>) TestUtils.getPropertyValue(evalContexts.get(2), "variables");
assertEquals(4, variables.size());
assertTrue(variables.containsKey("bar"));
assertTrue(variables.containsKey("barParent"));
assertTrue(variables.containsKey("barChild"));
assertTrue(variables.containsKey("jsonPath"));
// Test transformer expressions
child.getBean("input", MessageChannel.class).send(new GenericMessage<String>("baz"));

View File

@@ -39,6 +39,13 @@
</beans:property>
</beans:bean>
<beans:import resource="property-accessor-import-context.xml" />
<!-- import twice to verify override is ok -->
<beans:import resource="property-accessor-import-context.xml" />
<beans:bean id="fooAccessor" class="org.springframework.integration.transformer.SpelTransformerIntegrationTests$FooAccessor"/>
<channel id="fooin" />
<transformer id="foo" input-channel="fooin" expression="payload.bar" />

View File

@@ -21,10 +21,13 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.TypeDescriptor;
@@ -35,11 +38,13 @@ import org.springframework.expression.TypedValue;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.ReplyRequiredException;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
@@ -74,6 +79,11 @@ public class SpelTransformerIntegrationTests {
@Autowired
private MessageChannel spelFunctionInput;
@Autowired
private IntegrationEvaluationContextFactoryBean evaluationContextFactoryBean;
@Autowired
private BeanFactory beanFactory;
@Test
public void simple() {
@@ -111,6 +121,7 @@ public class SpelTransformerIntegrationTests {
assertNotNull(reply);
assertTrue(reply.getPayload() instanceof String);
assertEquals("baz", reply.getPayload());
assertEquals(4, TestUtils.getPropertyValue(this.evaluationContextFactoryBean, "propertyAccessors", List.class).size());
}
@Test

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
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">
<int:spel-property-accessors>
<bean id="fooAccessor1" class="org.springframework.integration.transformer.SpelTransformerIntegrationTests$FooAccessor"/>
<ref bean="fooAccessor"/>
</int:spel-property-accessors>
</beans>

View File

@@ -26,8 +26,10 @@ import java.net.SocketException;
import javax.net.SocketFactory;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.ip.util.TestingUtilities;
import org.springframework.test.context.ContextConfiguration;
@@ -89,6 +91,7 @@ public class SOLingerTests {
}
@Test
@Ignore
public void finReceivedNioLinger() {
finReceived(inCFNioLinger);
}

View File

@@ -35,8 +35,8 @@
<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.
In fact, the framework provides one such accessor,
<interfacename>PropertyAccessor</interfacename>s to the SpEL evaluation contexts used by
the framework. The framework provides
the <classname>JsonPropertyAccessor</classname> which can be used (read-only) to access fields from
a <classname>JsonNode</classname>, or JSON in a <classname>String</classname>. Or you can create your
own <interfacename>PropertyAccessor</interfacename> if you have specific needs.
@@ -47,10 +47,11 @@
expression used throughout the framework.
</para>
<para>
To configure your custom accessors and functions, add an
<classname>IntegrationEvaluationContextFactoryBean</classname> with
<code>id="integrationEvaluationContext"</code>
to your application context, with the appropriate configuration; for example:
The following configuration shows how to directly configure the
<classname>IntegrationEvaluationContextFactoryBean</classname> with custom property accessors
and functions. However, for convenience, namespace support is provided for both, as
described in the following sections, and the framework will automatically configure
the factory bean on your behalf.
</para>
<programlisting language="xml"><![CDATA[<beans:bean id="integrationEvaluationContext"
class="org.springframework.integration.config.IntegrationEvaluationContextFactoryBean">
@@ -92,8 +93,8 @@
<para>
Namespace support is provided for easy addition of SpEL custom functions.
You can specify <code>&lt;spel-function/&gt;</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.
<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.
@@ -123,7 +124,7 @@
<classname>StandardEvaluationContext</classname> instance,
and it is configured with the default
<interfacename>PropertyAccessor</interfacename>s, <interfacename>BeanResolver</interfacename>
and the custom function.
and the custom functions.
</listitem>
<listitem>
That <interfacename>EvaluationContext</interfacename> instance is injected into the
@@ -136,13 +137,85 @@
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.
declarations are inherited and can be overridden if needed by declaring a SpEL function with
the same name.
</note>
</para>
<para>
Spring Integration provides some standard functions, which are registered with the application
context automatically on start up:
<itemizedlist>
<listitem>
<emphasis role="bold">#jsonPath</emphasis> - to evaluate a 'jsonPath' on some provided object. This function
invokes <code>JsonPathUtils.evaluate(...)</code>. This static method delegates to the
<ulink url="http://code.google.com/p/json-path">Jayway JsonPath library</ulink>. The following shows
some usage examples:
<programlisting language="xml"><![CDATA[<transformer expression="#jsonPath(payload, '$.store.book[0].author')"/>
<filter expression="#jsonPath(payload,'$..book[2].isbn') matches '\d-\d{3}-\d{5}-\d'"/>
<splitter expression="#jsonPath(payload, '$.store.book')"/>
<router expression="#jsonPath(payload, headers.jsonPath)">
<mapping channel="output1" value="reference"/>
<mapping channel="output2" value="fiction"/>
</router>]]></programlisting>
#jsonPath also supports the third optional parameter - an array of
<ulink url="https://github.com/jayway/JsonPath/blob/master/json-path/src/main/java/com/jayway/jsonpath/Filter.java"
><classname>com.jayway.jsonpath.Filter</classname></ulink>, which could be provided by a reference to a
bean or bean method, for example.
<note>
Using this function requires the Jayway JsonPath library (json-path.jar) to be on
the classpath; otherwise the <emphasis>#jsonPath</emphasis>
SpEL function won't be registered.
</note>
For more information regarding JSON see 'JSON Transformers' in <xref linkend="transformer"/>.
</listitem>
<!--<listitem>
<emphasis>#xpath</emphasis> - TBD
</listitem>
<listitem>
<emphasis>#request</emphasis> - TBD
</listitem>
<listitem>
<emphasis>#auth</emphasis> - TBD
</listitem>-->
</itemizedlist>
</para>
</section>
<section id="spel-property-accessors">
<title>PropertyAccessors</title>
<para>
Namespace support is provided for the easy addition of SpEL custom
<ulink url="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/expression/PropertyAccessor.html"
><interfacename>PropertyAccessor</interfacename></ulink>
implementations. You can specify the <code>&lt;spel-property-accessors/&gt;</code> component to provide a list of
custom <interfacename>PropertyAccessor</interfacename>s 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 the accessors to the default
<emphasis>integrationEvaluationContext</emphasis> factory bean:
</para>
<programlisting language="xml"><![CDATA[<int:spel-property-accessors>
<bean id="jsonPA" class="org.springframework.integration.json.JsonPropertyAccessor"/>
<ref bean="fooPropertyAccessor"/>
</int:spel-property-accessors>
]]></programlisting>
<para>
With this sample, two custom <interfacename>PropertyAccessor</interfacename>s will be injected to the
<interfacename>EvaluationContext</interfacename> in the order that they are declared.
</para>
<para>
<note>
At this time, <interfacename>PropertyAccessor</interfacename>s are not inherited and must be
declared as described above in each context.
Custom <interfacename>PropertyAccessor</interfacename>s declared in a parent context are also made available
in any child context(s). They are placed at the end of result list (but before the default
<classname>org.springframework.context.expression.MapAccessor</classname> and
<classname>org.springframework.expression.spel.support.ReflectivePropertyAccessor</classname>).
If a <interfacename>PropertyAccessor</interfacename> with the same bean id is declared in a child context(s),
it will override the parent accessor. Beans declared within a <code>&lt;spel-property-accessors/&gt;</code>
must have an 'id' attribute.
The final order of usage is: the accessors in the current context,
in the order in which they are declared, followed by any from parent contexts, in order, followed
by the <classname>MapAccessor</classname> and finally the <classname>ReflectivePropertyAccessor</classname>.
</note>
</para>
</section>

View File

@@ -350,6 +350,10 @@ public class Foo {
<int-amqp:inbound-channel-adapter ... message-converter="jsonConverterWithPOType" ... />]]></programlisting>
</important>
<para>
In addition to JSON Transformers Spring Integration provides out-of-the-box <emphasis>#jsonPath</emphasis>
SpEL function. For more information see <xref linkend="spel"/>.
</para>
</section>
<section id="transformer-annotation">

View File

@@ -110,6 +110,15 @@
component is introduced. For more information see <xref linkend="spel-functions" />.
</para>
</section>
<section id="3.0-spel-property-accessors">
<title>SpEL PropertyAccessors Support</title>
<para>
To customize the SpEL <interfacename>EvaluationContext</interfacename> with
<interfacename>PropertyAccessor</interfacename> implementations
the new <code>&lt;spel-property-accessors/&gt;</code> component is introduced.
For more information see <xref linkend="spel-property-accessors" />.
</para>
</section>
</section>
<section id="3.0-general">