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

@@ -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>