GH-9436: Add support for SpEL IndexAccessor configuration (#9451)
Fixes: #9436 Issue link: https://github.com/spring-projects/spring-integration/issues/9436 * Expose `IndexAccessor` configuration options on the `AbstractEvaluationContextFactoryBean` and `SpelPropertyAccessorRegistrar` * Expose `<index-accessors>` sub-element for the `<spel-property-accessors>` * Adjust tests * Document the feature, including recently added `JsonIndexAccessor`
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018-2019 the original author or authors.
|
||||
* Copyright 2018-2024 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.
|
||||
@@ -28,11 +28,13 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.expression.IndexAccessor;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||
import org.springframework.integration.expression.SpelPropertyAccessorRegistrar;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -45,14 +47,19 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class AbstractEvaluationContextFactoryBean implements ApplicationContextAware, InitializingBean {
|
||||
|
||||
private Map<String, PropertyAccessor> propertyAccessors = new LinkedHashMap<String, PropertyAccessor>();
|
||||
private Map<String, PropertyAccessor> propertyAccessors = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, Method> functions = new LinkedHashMap<String, Method>();
|
||||
private Map<String, IndexAccessor> indexAccessors = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, Method> functions = new LinkedHashMap<>();
|
||||
|
||||
private TypeConverter typeConverter = new StandardTypeConverter();
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Nullable
|
||||
private SpelPropertyAccessorRegistrar propertyAccessorRegistrar;
|
||||
|
||||
private boolean initialized;
|
||||
|
||||
protected TypeConverter getTypeConverter() {
|
||||
@@ -68,22 +75,45 @@ public abstract class AbstractEvaluationContextFactoryBean implements Applicatio
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public void setPropertyAccessors(Map<String, PropertyAccessor> accessors) {
|
||||
public void setPropertyAccessors(Map<String, PropertyAccessor> propertyAccessors) {
|
||||
Assert.isTrue(!this.initialized, "'propertyAccessors' can't be changed after initialization.");
|
||||
Assert.notNull(accessors, "'accessors' must not be null.");
|
||||
Assert.noNullElements(accessors.values().toArray(), "'accessors' cannot have null values.");
|
||||
this.propertyAccessors = new LinkedHashMap<String, PropertyAccessor>(accessors);
|
||||
Assert.notNull(propertyAccessors, "'propertyAccessors' must not be null.");
|
||||
Assert.noNullElements(propertyAccessors.values().toArray(), "'propertyAccessors' cannot have null values.");
|
||||
this.propertyAccessors = new LinkedHashMap<>(propertyAccessors);
|
||||
}
|
||||
|
||||
public Map<String, PropertyAccessor> getPropertyAccessors() {
|
||||
return this.propertyAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a map of {@link IndexAccessor}s to use in the target {@link org.springframework.expression.EvaluationContext}
|
||||
* @param indexAccessors the map of {@link IndexAccessor}s to use
|
||||
* @since 6.4
|
||||
* @see org.springframework.expression.EvaluationContext#getIndexAccessors()
|
||||
*/
|
||||
public void setIndexAccessors(Map<String, IndexAccessor> indexAccessors) {
|
||||
Assert.isTrue(!this.initialized, "'indexAccessors' can't be changed after initialization.");
|
||||
Assert.notNull(indexAccessors, "'indexAccessors' must not be null.");
|
||||
Assert.noNullElements(indexAccessors.values().toArray(), "'indexAccessors' cannot have null values.");
|
||||
this.indexAccessors = new LinkedHashMap<>(indexAccessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map of {@link IndexAccessor}s to use in the target {@link org.springframework.expression.EvaluationContext}
|
||||
* @return the map of {@link IndexAccessor}s to use
|
||||
* @since 6.4
|
||||
* @see org.springframework.expression.EvaluationContext#getIndexAccessors()
|
||||
*/
|
||||
public Map<String, IndexAccessor> getIndexAccessors() {
|
||||
return this.indexAccessors;
|
||||
}
|
||||
|
||||
public void setFunctions(Map<String, Method> functionsArg) {
|
||||
Assert.isTrue(!this.initialized, "'functions' can't be changed after initialization.");
|
||||
Assert.notNull(functionsArg, "'functions' must not be null.");
|
||||
Assert.noNullElements(functionsArg.values().toArray(), "'functions' cannot have null values.");
|
||||
this.functions = new LinkedHashMap<String, Method>(functionsArg);
|
||||
this.functions = new LinkedHashMap<>(functionsArg);
|
||||
}
|
||||
|
||||
public Map<String, Method> getFunctions() {
|
||||
@@ -94,7 +124,14 @@ public abstract class AbstractEvaluationContextFactoryBean implements Applicatio
|
||||
if (this.applicationContext != null) {
|
||||
conversionService();
|
||||
functions();
|
||||
try {
|
||||
this.propertyAccessorRegistrar = this.applicationContext.getBean(SpelPropertyAccessorRegistrar.class);
|
||||
}
|
||||
catch (@SuppressWarnings("unused") NoSuchBeanDefinitionException e) {
|
||||
// There is no 'SpelPropertyAccessorRegistrar' bean in the application context.
|
||||
}
|
||||
propertyAccessors();
|
||||
indexAccessors();
|
||||
processParentIfPresent(beanName);
|
||||
}
|
||||
this.initialized = true;
|
||||
@@ -108,28 +145,43 @@ public abstract class AbstractEvaluationContextFactoryBean implements Applicatio
|
||||
}
|
||||
|
||||
private void functions() {
|
||||
Map<String, SpelFunctionFactoryBean> functionFactoryBeanMap = BeanFactoryUtils
|
||||
.beansOfTypeIncludingAncestors(this.applicationContext, SpelFunctionFactoryBean.class);
|
||||
for (SpelFunctionFactoryBean spelFunctionFactoryBean : functionFactoryBeanMap.values()) {
|
||||
if (!getFunctions().containsKey(spelFunctionFactoryBean.getFunctionName())) {
|
||||
getFunctions().put(spelFunctionFactoryBean.getFunctionName(), spelFunctionFactoryBean.getObject());
|
||||
Map<String, SpelFunctionFactoryBean> spelFunctions =
|
||||
BeanFactoryUtils.beansOfTypeIncludingAncestors(this.applicationContext, SpelFunctionFactoryBean.class);
|
||||
for (SpelFunctionFactoryBean spelFunctionFactoryBean : spelFunctions.values()) {
|
||||
String functionName = spelFunctionFactoryBean.getFunctionName();
|
||||
if (!this.functions.containsKey(functionName)) {
|
||||
this.functions.put(functionName, spelFunctionFactoryBean.getObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void propertyAccessors() {
|
||||
try {
|
||||
SpelPropertyAccessorRegistrar propertyAccessorRegistrar =
|
||||
this.applicationContext.getBean(SpelPropertyAccessorRegistrar.class);
|
||||
for (Entry<String, PropertyAccessor> entry : propertyAccessorRegistrar.getPropertyAccessors()
|
||||
.entrySet()) {
|
||||
if (!getPropertyAccessors().containsKey(entry.getKey())) {
|
||||
getPropertyAccessors().put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (this.propertyAccessorRegistrar != null) {
|
||||
propertyAccessors(this.propertyAccessorRegistrar.getPropertyAccessors());
|
||||
}
|
||||
}
|
||||
|
||||
private void propertyAccessors(Map<String, PropertyAccessor> propertyAccessors) {
|
||||
for (Entry<String, PropertyAccessor> entry : propertyAccessors.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (!this.propertyAccessors.containsKey(key)) {
|
||||
this.propertyAccessors.put(key, entry.getValue());
|
||||
}
|
||||
}
|
||||
catch (@SuppressWarnings("unused") NoSuchBeanDefinitionException e) {
|
||||
// There is no 'SpelPropertyAccessorRegistrar' bean in the application context.
|
||||
}
|
||||
|
||||
private void indexAccessors() {
|
||||
if (this.propertyAccessorRegistrar != null) {
|
||||
indexAccessors(this.propertyAccessorRegistrar.getIndexAccessors());
|
||||
}
|
||||
}
|
||||
|
||||
private void indexAccessors(Map<String, IndexAccessor> indexAccessors) {
|
||||
for (Entry<String, IndexAccessor> entry : indexAccessors.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (!this.indexAccessors.containsKey(key)) {
|
||||
this.indexAccessors.put(key, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,16 +190,12 @@ public abstract class AbstractEvaluationContextFactoryBean implements Applicatio
|
||||
|
||||
if (parent != null && parent.containsBean(beanName)) {
|
||||
AbstractEvaluationContextFactoryBean parentFactoryBean = parent.getBean("&" + beanName, getClass());
|
||||
|
||||
for (Entry<String, PropertyAccessor> entry : parentFactoryBean.getPropertyAccessors().entrySet()) {
|
||||
if (!getPropertyAccessors().containsKey(entry.getKey())) {
|
||||
getPropertyAccessors().put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
propertyAccessors(parentFactoryBean.getPropertyAccessors());
|
||||
indexAccessors(parentFactoryBean.getIndexAccessors());
|
||||
for (Entry<String, Method> entry : parentFactoryBean.getFunctions().entrySet()) {
|
||||
if (!getFunctions().containsKey(entry.getKey())) {
|
||||
getFunctions().put(entry.getKey(), entry.getValue());
|
||||
String key = entry.getKey();
|
||||
if (!this.functions.containsKey(key)) {
|
||||
this.functions.put(key, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2019 the original author or authors.
|
||||
* Copyright 2013-2024 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.
|
||||
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.expression.IndexAccessor;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypeLocator;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
@@ -102,6 +103,10 @@ public class IntegrationEvaluationContextFactoryBean extends AbstractEvaluationC
|
||||
|
||||
evaluationContext.addPropertyAccessor(new MapAccessor());
|
||||
|
||||
for (IndexAccessor indexAccessor : getIndexAccessors().values()) {
|
||||
evaluationContext.addIndexAccessor(indexAccessor);
|
||||
}
|
||||
|
||||
for (Entry<String, Method> functionEntry : getFunctions().entrySet()) {
|
||||
evaluationContext.registerFunction(functionEntry.getKey(), functionEntry.getValue());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.expression.IndexAccessor;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.spel.support.DataBindingPropertyAccessor;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
@@ -80,6 +81,7 @@ public class IntegrationSimpleEvaluationContextFactoryBean extends AbstractEvalu
|
||||
accessorArray[accessors.size() + 1] = DataBindingPropertyAccessor.forReadOnlyAccess();
|
||||
SimpleEvaluationContext evaluationContext =
|
||||
SimpleEvaluationContext.forPropertyAccessors(accessorArray)
|
||||
.withIndexAccessors(getIndexAccessors().values().toArray(new IndexAccessor[0]))
|
||||
.withTypeConverter(getTypeConverter())
|
||||
.withInstanceMethods()
|
||||
.withAssignmentDisabled()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2024 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.
|
||||
@@ -16,13 +16,10 @@
|
||||
|
||||
package org.springframework.integration.config.xml;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
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;
|
||||
@@ -33,7 +30,9 @@ import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.expression.SpelPropertyAccessorRegistrar;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
|
||||
/**
|
||||
* Parser for the <spel-property-accessors> element.
|
||||
@@ -46,69 +45,61 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class SpelPropertyAccessorsParser implements BeanDefinitionParser {
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
private final Map<String, Object> propertyAccessors = new ManagedMap<String, Object>();
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
initializeSpelPropertyAccessorRegistrarIfNecessary(parserContext);
|
||||
Map<String, Object> propertyAccessors = new ManagedMap<>();
|
||||
Map<String, Object> indexAccessors = new ManagedMap<>();
|
||||
parseTargetedAccessors(element, parserContext, propertyAccessors);
|
||||
|
||||
BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
|
||||
|
||||
NodeList children = element.getChildNodes();
|
||||
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
String propertyAccessorName;
|
||||
Object propertyAccessor;
|
||||
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(); // NOSONAR not null
|
||||
propertyAccessor = propertyAccessorRef;
|
||||
}
|
||||
else {
|
||||
parserContext.getReaderContext().error("Only '<bean>' and '<ref>' elements are allowed.", element);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.propertyAccessors.put(propertyAccessorName, propertyAccessor);
|
||||
}
|
||||
Element indexAccessorsElement = DomUtils.getChildElementByTagName(element, "index-accessors");
|
||||
if (indexAccessorsElement != null) {
|
||||
parseTargetedAccessors(indexAccessorsElement, parserContext, indexAccessors);
|
||||
}
|
||||
|
||||
BeanDefinitionBuilder registrarBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(SpelPropertyAccessorRegistrar.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
if (!CollectionUtils.isEmpty(propertyAccessors)) {
|
||||
registrarBuilder.addConstructorArgValue(propertyAccessors);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(indexAccessors)) {
|
||||
registrarBuilder.addPropertyValue("indexAccessors", indexAccessors);
|
||||
}
|
||||
|
||||
parserContext.getRegistry()
|
||||
.registerBeanDefinition(IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME,
|
||||
registrarBuilder.getBeanDefinition());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void initializeSpelPropertyAccessorRegistrarIfNecessary(ParserContext parserContext) {
|
||||
this.lock.lock();
|
||||
try {
|
||||
if (!parserContext.getRegistry()
|
||||
.containsBeanDefinition(IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME)) {
|
||||
private static void parseTargetedAccessors(Element accessorsElement, ParserContext parserContext,
|
||||
Map<String, Object> accessorsMap) {
|
||||
|
||||
BeanDefinitionBuilder registrarBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(SpelPropertyAccessorRegistrar.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
.addConstructorArgValue(this.propertyAccessors);
|
||||
parserContext.getRegistry()
|
||||
.registerBeanDefinition(IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME,
|
||||
registrarBuilder.getBeanDefinition());
|
||||
BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
|
||||
List<Element> accessorElements = DomUtils.getChildElementsByTagName(accessorsElement,
|
||||
BeanDefinitionParserDelegate.BEAN_ELEMENT, BeanDefinitionParserDelegate.REF_ELEMENT);
|
||||
for (Element accessorElement : accessorElements) {
|
||||
String accessorName;
|
||||
Object accessor;
|
||||
if (delegate.nodeNameEquals(accessorElement, BeanDefinitionParserDelegate.BEAN_ELEMENT)) {
|
||||
accessorName = accessorElement.getAttribute(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
|
||||
if (!StringUtils.hasText(accessorName)) {
|
||||
parserContext.getReaderContext()
|
||||
.error("The '<bean>' 'id' attribute is required within 'spel-property-accessors'.",
|
||||
accessorElement);
|
||||
return;
|
||||
}
|
||||
accessor = delegate.parseBeanDefinitionElement(accessorElement);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.lock.unlock();
|
||||
else {
|
||||
BeanReference propertyAccessorRef =
|
||||
(BeanReference) delegate.parsePropertySubElement(accessorElement, null);
|
||||
accessorName = propertyAccessorRef.getBeanName(); // NOSONAR not null
|
||||
accessor = propertyAccessorRef;
|
||||
}
|
||||
accessorsMap.put(accessorName, accessor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2017-2024 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.
|
||||
@@ -20,7 +20,9 @@ import java.beans.Introspector;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.expression.IndexAccessor;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TargetedAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -35,14 +37,16 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class SpelPropertyAccessorRegistrar {
|
||||
|
||||
private final Map<String, PropertyAccessor> propertyAccessors = new LinkedHashMap<String, PropertyAccessor>();
|
||||
private final Map<String, PropertyAccessor> propertyAccessors = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, IndexAccessor> indexAccessors = new LinkedHashMap<>();
|
||||
|
||||
public SpelPropertyAccessorRegistrar() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with the provided property accessors. Each accessor name
|
||||
* will be the class simple name.
|
||||
* Create an instance with the provided {@link PropertyAccessor} instances.
|
||||
* Each accessor name will be the class simple name.
|
||||
* @param propertyAccessors the accessors.
|
||||
* @since 4.3.8
|
||||
*/
|
||||
@@ -54,7 +58,7 @@ public class SpelPropertyAccessorRegistrar {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with the provided named property accessors.
|
||||
* Create an instance with the provided named {@link PropertyAccessor} instances.
|
||||
* @param propertyAccessors a map of name:accessor.
|
||||
* @since 4.3.8
|
||||
*/
|
||||
@@ -64,7 +68,8 @@ public class SpelPropertyAccessorRegistrar {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the registered accessors.
|
||||
* Return the registered {@link PropertyAccessor} instances to use
|
||||
* in the target {@link org.springframework.expression.EvaluationContext}.
|
||||
* @return the map of name:accessor.
|
||||
* @since 4.3.8
|
||||
*/
|
||||
@@ -72,6 +77,26 @@ public class SpelPropertyAccessorRegistrar {
|
||||
return this.propertyAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a map of {@link IndexAccessor} instances to use
|
||||
* in the target {@link org.springframework.expression.EvaluationContext}.
|
||||
* @param indexAccessors the map of name:accessor.
|
||||
* @since 6.4
|
||||
*/
|
||||
public void setIndexAccessors(Map<String, IndexAccessor> indexAccessors) {
|
||||
Assert.notEmpty(indexAccessors, "'indexAccessors' must not be empty");
|
||||
this.indexAccessors.putAll(indexAccessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the registered {@link IndexAccessor} instances.
|
||||
* @return the map of name:accessor.
|
||||
* @since 6.4
|
||||
*/
|
||||
public Map<String, IndexAccessor> getIndexAccessors() {
|
||||
return this.indexAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided named property accessor.
|
||||
* @param name the name.
|
||||
@@ -101,8 +126,37 @@ public class SpelPropertyAccessorRegistrar {
|
||||
return this;
|
||||
}
|
||||
|
||||
private static String obtainAccessorKey(PropertyAccessor propertyAccessor) {
|
||||
return Introspector.decapitalize(propertyAccessor.getClass().getSimpleName());
|
||||
/**
|
||||
* Add the provided named {@link IndexAccessor}.
|
||||
* @param name the name.
|
||||
* @param indexAccessor the accessor.
|
||||
* @return this registrar.
|
||||
* @since 6.4
|
||||
*/
|
||||
public SpelPropertyAccessorRegistrar add(String name, IndexAccessor indexAccessor) {
|
||||
Assert.hasText(name, "'name' must not be empty");
|
||||
Assert.notNull(indexAccessor, "'indexAccessor' must not be null");
|
||||
this.indexAccessors.put(name, indexAccessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided {@link IndexAccessor} instances.
|
||||
* Each accessor name will be the class simple name.
|
||||
* @param indexAccessors the accessors.
|
||||
* @return this registrar.
|
||||
* @since 6.4
|
||||
*/
|
||||
public SpelPropertyAccessorRegistrar add(IndexAccessor... indexAccessors) {
|
||||
Assert.notEmpty(indexAccessors, "'indexAccessors' must not be empty");
|
||||
for (IndexAccessor indexAccessor : indexAccessors) {
|
||||
this.indexAccessors.put(obtainAccessorKey(indexAccessor), indexAccessor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static String obtainAccessorKey(TargetedAccessor targetedAccessor) {
|
||||
return Introspector.decapitalize(targetedAccessor.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4698,10 +4698,20 @@ The list of component name patterns you want to track (e.g., tracked-components
|
||||
</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:sequence>
|
||||
<xsd:element name="index-accessors" minOccurs="0">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element ref="beans:bean"/>
|
||||
<xsd:element ref="beans:ref"/>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element ref="beans:bean"/>
|
||||
<xsd:element ref="beans:ref"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.core.serializer.support.SerializingConverter;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.IndexAccessor;
|
||||
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
@@ -112,6 +112,7 @@ import org.springframework.integration.handler.ServiceActivatingHandler;
|
||||
import org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.history.MessageHistoryConfigurer;
|
||||
import org.springframework.integration.json.JsonIndexAccessor;
|
||||
import org.springframework.integration.json.JsonPropertyAccessor;
|
||||
import org.springframework.integration.scheduling.PollerMetadata;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
@@ -786,15 +787,17 @@ public class EnableIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void testIntegrationEvaluationContextCustomization() {
|
||||
EvaluationContext evaluationContext = this.context.getBean(StandardEvaluationContext.class);
|
||||
List<?> propertyAccessors = TestUtils.getPropertyValue(evaluationContext, "propertyAccessors", List.class);
|
||||
StandardEvaluationContext evaluationContext = this.context.getBean(StandardEvaluationContext.class);
|
||||
List<?> propertyAccessors = evaluationContext.getPropertyAccessors();
|
||||
assertThat(propertyAccessors.size()).isEqualTo(4);
|
||||
assertThat(propertyAccessors.get(0)).isInstanceOf(JsonPropertyAccessor.class);
|
||||
assertThat(propertyAccessors.get(1)).isInstanceOf(EnvironmentAccessor.class);
|
||||
assertThat(propertyAccessors.get(2)).isInstanceOf(MapAccessor.class);
|
||||
assertThat(propertyAccessors.get(3)).isInstanceOf(ReflectivePropertyAccessor.class);
|
||||
Map<?, ?> variables = TestUtils.getPropertyValue(evaluationContext, "variables", Map.class);
|
||||
Object testSpelFunction = variables.get("testSpelFunction");
|
||||
List<IndexAccessor> indexAccessors = evaluationContext.getIndexAccessors();
|
||||
assertThat(indexAccessors.size()).isEqualTo(1);
|
||||
assertThat(indexAccessors.get(0)).isInstanceOf(JsonIndexAccessor.class);
|
||||
Object testSpelFunction = evaluationContext.lookupVariable("testSpelFunction");
|
||||
assertThat(testSpelFunction).isEqualTo(ClassUtils.getStaticMethod(TestSpelFunction.class, "bar",
|
||||
Object.class));
|
||||
}
|
||||
@@ -1244,7 +1247,9 @@ public class EnableIntegrationTests {
|
||||
|
||||
@Bean
|
||||
public SpelPropertyAccessorRegistrar spelPropertyAccessorRegistrar() {
|
||||
return new SpelPropertyAccessorRegistrar(new JsonPropertyAccessor(), new EnvironmentAccessor());
|
||||
return new SpelPropertyAccessorRegistrar(new JsonPropertyAccessor())
|
||||
.add(new EnvironmentAccessor())
|
||||
.add(new JsonIndexAccessor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -42,10 +42,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" />
|
||||
<spel-property-accessors>
|
||||
<index-accessors>
|
||||
<beans:bean id="jsonIndex" class="org.springframework.integration.json.JsonIndexAccessor"/>
|
||||
</index-accessors>
|
||||
<beans:bean id="fooAccessor1" class="org.springframework.integration.transformer.SpelTransformerIntegrationTests$FooAccessor"/>
|
||||
<beans:ref bean="fooAccessor"/>
|
||||
</spel-property-accessors>
|
||||
|
||||
<beans:bean id="fooAccessor" class="org.springframework.integration.transformer.SpelTransformerIntegrationTests$FooAccessor"/>
|
||||
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
|
||||
package org.springframework.integration.transformer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -33,13 +30,12 @@ import org.springframework.integration.config.IntegrationEvaluationContextFactor
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.handler.ReplyRequiredException;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -49,8 +45,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class SpelTransformerIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
@@ -96,7 +92,7 @@ public class SpelTransformerIntegrationTests {
|
||||
@Test
|
||||
public void testInt2755ChainChildIdWithinExceptionMessage() {
|
||||
try {
|
||||
this.transformerChainInput.send(new GenericMessage<String>("foo"));
|
||||
this.transformerChainInput.send(new GenericMessage<>("foo"));
|
||||
}
|
||||
catch (ReplyRequiredException e) {
|
||||
assertThat(e.getMessage()).contains("No reply produced by handler 'transformerChain$child#0'");
|
||||
@@ -108,20 +104,20 @@ public class SpelTransformerIntegrationTests {
|
||||
QueueChannel outputChannel = new QueueChannel();
|
||||
fooHandler.setOutputChannel(outputChannel);
|
||||
Foo foo = new Foo("baz");
|
||||
fooHandler.handleMessage(new GenericMessage<Foo>(foo));
|
||||
fooHandler.handleMessage(new GenericMessage<>(foo));
|
||||
Message<?> reply = outputChannel.receive(0);
|
||||
assertThat(reply).isNotNull();
|
||||
assertThat(reply.getPayload() instanceof String).isTrue();
|
||||
assertThat(reply.getPayload()).isEqualTo("baz");
|
||||
assertThat(TestUtils.getPropertyValue(this.evaluationContextFactoryBean, "propertyAccessors", Map.class).size())
|
||||
.isEqualTo(3);
|
||||
assertThat(this.evaluationContextFactoryBean.getPropertyAccessors()).hasSize(3);
|
||||
assertThat(this.evaluationContextFactoryBean.getIndexAccessors()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomFunction() {
|
||||
QueueChannel outputChannel = new QueueChannel();
|
||||
barHandler.setOutputChannel(outputChannel);
|
||||
barHandler.handleMessage(new GenericMessage<String>("foo"));
|
||||
barHandler.handleMessage(new GenericMessage<>("foo"));
|
||||
Message<?> reply = outputChannel.receive(0);
|
||||
assertThat(reply).isNotNull();
|
||||
assertThat(reply.getPayload()).isEqualTo("bar");
|
||||
@@ -169,7 +165,7 @@ public class SpelTransformerIntegrationTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
public boolean canRead(EvaluationContext context, Object target, String name) {
|
||||
return "bar".equals(name);
|
||||
}
|
||||
|
||||
@@ -180,7 +176,7 @@ public class SpelTransformerIntegrationTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) {
|
||||
return "bar".equals(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://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>
|
||||
@@ -20,6 +20,11 @@ Starting with Spring Integration 3.0, you can add additional `PropertyAccessor`
|
||||
The framework provides the (read-only) `JsonPropertyAccessor`, which you can use to access fields from a `JsonNode` or JSON in a `String`.
|
||||
You can also create your own `PropertyAccessor` if you have specific needs.
|
||||
|
||||
Starting with version 6.4, the `JsonIndexAccessor` implementation is provided that knows how to read indexes from JSON arrays, using Jackson's `ArrayNode` API.
|
||||
Supports indexes supplied as an integer literal, for example, `myJsonArray[1]`.
|
||||
Also supports negative indexes, for example, `myJsonArray[-1]` which equates to `myJsonArray[myJsonArray.length - 1]`.
|
||||
Furthermore, `null` is returned for any index that is out of bounds (see `ArrayNode.get(int)` for details).
|
||||
|
||||
In addition, you can add custom functions.
|
||||
Custom functions are `static` methods declared on a class.
|
||||
Functions and property accessors are available in any SpEL expression used throughout the framework.
|
||||
@@ -45,6 +50,9 @@ The following configuration shows how to directly configure the `IntegrationEval
|
||||
</bean>
|
||||
----
|
||||
|
||||
Starting with version 6.4, the `AbstractEvaluationContextFactoryBean` supports an injection of `IndexAccessor` instances.
|
||||
See `AbstractEvaluationContextFactoryBean` method JavaDocs for more information.
|
||||
|
||||
For convenience, Spring Integration provides namespace support for both property accessors and functions, as described in the following sections.
|
||||
The framework automatically configures the factory bean on your behalf.
|
||||
|
||||
@@ -122,7 +130,7 @@ Each context has its own instance of the `integrationEvaluationContext` factory
|
||||
[[built-in-spel-functions]]
|
||||
=== Built-in SpEL Functions
|
||||
|
||||
Spring Integration provides the folloiwng standard functions, which are registered with the application context automatically on start up:
|
||||
Spring Integration provides the following standard functions, which are registered with the application context automatically on start up:
|
||||
|
||||
* `#jsonPath`: Evaluates a 'jsonPath' on a specified object.
|
||||
This function invokes `JsonPathUtils.evaluate(...)`, which delegates to the https://github.com/json-path/JsonPath[Jayway JsonPath library].
|
||||
@@ -157,12 +165,16 @@ For more information regarding XML and XPath, see xref:xml.adoc[XML Support - De
|
||||
|
||||
Spring Integration provides namespace support to let you create SpEL custom https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/expression/PropertyAccessor.html[`PropertyAccessor`] implementations.
|
||||
You can use the `<spel-property-accessors/>` component to provide a list of custom `PropertyAccessor` instances to the `EvaluationContext` used throughout the framework.
|
||||
Instead of configuring the factory bean shown earlier, you can add one or more of these components, and the framework automatically adds the accessors to the default `integrationEvaluationContext` factory bean.
|
||||
Instead of configuring the factory bean shown earlier, you can add this component, and the framework automatically adds the accessors to the default `integrationEvaluationContext` factory bean.
|
||||
Also, starting with version 6.4, a dedicated `<index-accessors>` sub-element is provided to configure `IndexAccessor` beans similar way.
|
||||
The following example shows how to do so:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<int:spel-property-accessors>
|
||||
<index-accessors>
|
||||
<beans:bean id="jsonIndex" class="org.springframework.integration.json.JsonIndexAccessor"/>
|
||||
</index-accessors>
|
||||
<bean id="jsonPA" class="org.springframework.integration.json.JsonPropertyAccessor"/>
|
||||
<ref bean="fooPropertyAccessor"/>
|
||||
</int:spel-property-accessors>
|
||||
@@ -170,15 +182,17 @@ The following example shows how to do so:
|
||||
|
||||
In the preceding example, two custom `PropertyAccessor` instances are injected into the `EvaluationContext` (in the order in which they are declared).
|
||||
|
||||
|
||||
To provide `PropertyAccessor` instances by using Java Configuration, you should declare a `SpelPropertyAccessorRegistrar` bean with a name of `spelPropertyAccessorRegistrar` (dictated by the `IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME` constant).
|
||||
The following example shows how to configure two custom `PropertyAccessor` instances with Java:
|
||||
The following example shows how to configure two custom `PropertyAccessor` (and `IndexAccessor` starting with version 6.4) instances with Java:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SpelPropertyAccessorRegistrar spelPropertyAccessorRegistrar() {
|
||||
return new SpelPropertyAccessorRegistrar(new JsonPropertyAccessor())
|
||||
.add(fooPropertyAccessor());
|
||||
.add(fooPropertyAccessor())
|
||||
.add(new JsonIndexAccessor());
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ See xref:control-bus.adoc[Control Bus] for more information.
|
||||
Also, a `ControlBusController` (together with an `@EnableControlBusController`) is introduced for managing exposed commands by the mentioned `ControlBusCommandRegistry`.
|
||||
See xref:http.adoc[HTTP Support] for more information.
|
||||
|
||||
The SpEL evaluation infrastructure now supports configuration for `IndexAccessor`.
|
||||
Also, an out-of-the-box `JsonIndexAccessor` is provided.
|
||||
See xref:spel.adoc[SpEL Support] for more information.
|
||||
|
||||
[[x6.4-general]]
|
||||
=== General Changes
|
||||
|
||||
@@ -74,4 +78,3 @@ See xref:sftp/session-factory.adoc[SFTP Session Factory] for more information.
|
||||
Multiple instances of `MqttPahoMessageDrivenChannelAdapter` and `Mqttv5PahoMessageDrivenChannelAdapter` can now be added at runtime using corresponding `ClientManager` through `IntegrationFlowContext`
|
||||
Also a `MqttMessageNotDeliveredEvent` event has been introduced to emit when action callback reacts to the delivery failure.
|
||||
See xref:mqtt.adoc[MQTT Support] for more information.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user