INT-4443: Use SimpleEC for uriVariablesExpression

JIRA: https://jira.spring.io/browse/INT-4443

**cherry-pick to 5.0.x, 4.3.x**

Polishing; use data binding accessor in test evaluation contexts.

Add `.withInstanceMethods()`

See https://jira.spring.io/browse/SPR-16588?focusedCommentId=158041&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-158041

* Polishing according PR comments

# Conflicts:
#	build.gradle
#	src/reference/asciidoc/changes-4.3-5.0.adoc

# Conflicts:
#	build.gradle
#	spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionUtils.java
#	spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java
#	spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java
#	spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java
#	spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-4.3.xsd
#	spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java
#	spring-integration-http/src/test/java/org/springframework/integration/http/outbound/UriVariableExpressionTests.java
#	src/reference/asciidoc/changes-4.2-4.3.adoc
#	src/reference/asciidoc/http.adoc
This commit is contained in:
Gary Russell
2018-03-23 16:03:22 -04:00
committed by Artem Bilan
parent ed79a9521c
commit 705d33a30c
16 changed files with 514 additions and 157 deletions

View File

@@ -136,7 +136,7 @@ subprojects { subproject ->
springSecurityVersion = '4.1.4.RELEASE'
springSocialTwitterVersion = '1.1.2.RELEASE'
springRetryVersion = '1.1.3.RELEASE'
springVersion = project.hasProperty('springVersion') ? project.springVersion : '4.3.14.RELEASE'
springVersion = project.hasProperty('springVersion') ? project.springVersion : '4.3.15.BUILD-SNAPSHOT'
springWsVersion = '2.3.0.RELEASE'
xmlUnitVersion = '1.6'
xstreamVersion = '1.4.7'

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
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.core.convert.ConversionService;
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.util.Assert;
/**
* Abstract class for integration evaluation context factory beans.
*
* @author Gary Russell
*
* @since 4.3.15
*
*/
public abstract class AbstractEvaluationContextFactoryBean implements ApplicationContextAware, InitializingBean {
private Map<String, PropertyAccessor> propertyAccessors = new LinkedHashMap<String, PropertyAccessor>();
private Map<String, Method> functions = new LinkedHashMap<String, Method>();
private TypeConverter typeConverter = new StandardTypeConverter();
private ApplicationContext applicationContext;
private boolean initialized;
protected TypeConverter getTypeConverter() {
return this.typeConverter;
}
protected ApplicationContext getApplicationContext() {
return this.applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void setPropertyAccessors(Map<String, PropertyAccessor> accessors) {
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);
}
public Map<String, PropertyAccessor> getPropertyAccessors() {
return this.propertyAccessors;
}
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);
}
public Map<String, Method> getFunctions() {
return this.functions;
}
protected void initialize(String beanName) throws Exception {
if (this.applicationContext != null) {
ConversionService conversionService = IntegrationUtils.getConversionService(getApplicationContext());
if (conversionService != null) {
this.typeConverter = new StandardTypeConverter(conversionService);
}
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());
}
}
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());
}
}
}
catch (NoSuchBeanDefinitionException e) {
// There is no 'SpelPropertyAccessorRegistrar' bean in the application context.
}
ApplicationContext parent = this.applicationContext.getParent();
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());
}
}
for (Entry<String, Method> entry : parentFactoryBean.getFunctions().entrySet()) {
if (!getFunctions().containsKey(entry.getKey())) {
getFunctions().put(entry.getKey(), entry.getValue());
}
}
}
}
this.initialized = true;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2017 the original author or authors.
* Copyright 2013-2018 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.
@@ -17,17 +17,9 @@
package org.springframework.integration.config;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
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;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
@@ -36,11 +28,8 @@ import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.expression.SpelPropertyAccessorRegistrar;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.util.Assert;
/**
* <p>
@@ -73,107 +62,28 @@ import org.springframework.util.Assert;
*
* @since 3.0
*/
public class IntegrationEvaluationContextFactoryBean implements FactoryBean<StandardEvaluationContext>,
ApplicationContextAware, InitializingBean {
private volatile Map<String, PropertyAccessor> propertyAccessors = new LinkedHashMap<String, PropertyAccessor>();
private volatile Map<String, Method> functions = new LinkedHashMap<String, Method>();
private TypeConverter typeConverter = new StandardTypeConverter();
public class IntegrationEvaluationContextFactoryBean extends AbstractEvaluationContextFactoryBean
implements FactoryBean<StandardEvaluationContext> {
private volatile TypeLocator typeLocator;
private BeanResolver beanResolver;
private ApplicationContext applicationContext;
private volatile boolean initialized;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void setPropertyAccessors(Map<String, PropertyAccessor> accessors) {
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);
}
public Map<String, PropertyAccessor> getPropertyAccessors() {
return this.propertyAccessors;
}
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);
}
public Map<String, Method> getFunctions() {
return this.functions;
}
public void setTypeLocator(TypeLocator typeLocator) {
this.typeLocator = typeLocator;
}
@Override
public boolean isSingleton() {
return false;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.applicationContext != null) {
this.beanResolver = new BeanFactoryResolver(this.applicationContext);
ConversionService conversionService = IntegrationUtils.getConversionService(this.applicationContext);
if (conversionService != null) {
this.typeConverter = new StandardTypeConverter(conversionService);
}
Map<String, SpelFunctionFactoryBean> functionFactoryBeanMap =
BeanFactoryUtils.beansOfTypeIncludingAncestors(this.applicationContext, SpelFunctionFactoryBean.class);
for (SpelFunctionFactoryBean spelFunctionFactoryBean : functionFactoryBeanMap.values()) {
if (!this.functions.containsKey(spelFunctionFactoryBean.getFunctionName())) {
this.functions.put(spelFunctionFactoryBean.getFunctionName(), spelFunctionFactoryBean.getObject());
}
}
try {
SpelPropertyAccessorRegistrar propertyAccessorRegistrar =
this.applicationContext.getBean(SpelPropertyAccessorRegistrar.class);
for (Entry<String, PropertyAccessor> entry : propertyAccessorRegistrar.getPropertyAccessors().entrySet()) {
if (!this.propertyAccessors.containsKey(entry.getKey())) {
this.propertyAccessors.put(entry.getKey(), entry.getValue());
}
}
}
catch (NoSuchBeanDefinitionException e) {
// There is no 'SpelPropertyAccessorRegistrar' bean in the application context.
}
ApplicationContext parent = this.applicationContext.getParent();
if (parent != null && parent.containsBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)) {
IntegrationEvaluationContextFactoryBean parentFactoryBean =
parent.getBean("&" + IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME,
IntegrationEvaluationContextFactoryBean.class);
for (Entry<String, PropertyAccessor> entry : parentFactoryBean.getPropertyAccessors().entrySet()) {
if (!this.propertyAccessors.containsKey(entry.getKey())) {
this.propertyAccessors.put(entry.getKey(), entry.getValue());
}
}
for (Entry<String, Method> entry : parentFactoryBean.getFunctions().entrySet()) {
if (!this.functions.containsKey(entry.getKey())) {
this.functions.put(entry.getKey(), entry.getValue());
}
}
}
if (getApplicationContext() != null) {
this.beanResolver = new BeanFactoryResolver(getApplicationContext());
}
this.initialized = true;
initialize(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
}
@Override
@@ -184,15 +94,15 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean<Stan
}
evaluationContext.setBeanResolver(this.beanResolver);
evaluationContext.setTypeConverter(this.typeConverter);
evaluationContext.setTypeConverter(getTypeConverter());
for (PropertyAccessor propertyAccessor : this.propertyAccessors.values()) {
for (PropertyAccessor propertyAccessor : getPropertyAccessors().values()) {
evaluationContext.addPropertyAccessor(propertyAccessor);
}
evaluationContext.addPropertyAccessor(new MapAccessor());
for (Entry<String, Method> functionEntry : this.functions.entrySet()) {
for (Entry<String, Method> functionEntry : getFunctions().entrySet()) {
evaluationContext.registerFunction(functionEntry.getKey(), functionEntry.getValue());
}
@@ -204,9 +114,4 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean<Stan
return StandardEvaluationContext.class;
}
@Override
public boolean isSingleton() {
return false;
}
}

View File

@@ -194,6 +194,17 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
".expression.IntegrationEvaluationContextAwareBeanPostProcessor");
BeanDefinitionReaderUtils.registerWithGeneratedName(integrationEvalContextBPP, registry);
}
if (!registry.containsBeanDefinition(IntegrationContextUtils.INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME)) {
BeanDefinitionBuilder integrationEvaluationContextBuilder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationSimpleEvaluationContextFactoryBean.class);
integrationEvaluationContextBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
BeanDefinitionHolder integrationEvaluationContextHolder =
new BeanDefinitionHolder(integrationEvaluationContextBuilder.getBeanDefinition(),
IntegrationContextUtils.INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(integrationEvaluationContextHolder, registry);
}
}
/**

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map.Entry;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.support.DataBindingPropertyAccessor;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.expression.SpelPropertyAccessorRegistrar;
/**
* <p>
* {@link FactoryBean} to populate {@link SimpleEvaluationContext} instances enhanced with:
* <ul>
* <li>
* a {@link TypeConverter} based on the {@link ConversionService} from the application context.
* </li>
* <li>
* a set of provided {@link PropertyAccessor}s including a default {@link MapAccessor}.
* </li>
* <li>
* a set of provided SpEL functions.
* </li>
* </ul>
* <p>
* 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
*
* @since 4.3.15
*/
public class IntegrationSimpleEvaluationContextFactoryBean extends AbstractEvaluationContextFactoryBean
implements FactoryBean<SimpleEvaluationContext> {
@Override
public boolean isSingleton() {
return false;
}
@Override
public void afterPropertiesSet() throws Exception {
initialize(IntegrationContextUtils.INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME);
}
@Override
public SimpleEvaluationContext getObject() throws Exception {
Collection<PropertyAccessor> accessors = getPropertyAccessors().values();
PropertyAccessor[] accessorArray = accessors.toArray(new PropertyAccessor[accessors.size() + 2]);
accessorArray[accessors.size()] = new MapAccessor();
accessorArray[accessors.size() + 1] = DataBindingPropertyAccessor.forReadOnlyAccess();
SimpleEvaluationContext evaluationContext =
SimpleEvaluationContext.forPropertyAccessors(accessorArray)
.withTypeConverter(getTypeConverter())
.withInstanceMethods()
.build();
for (Entry<String, Method> functionEntry : getFunctions().entrySet()) {
evaluationContext.setVariable(functionEntry.getKey(), functionEntry.getValue());
}
return evaluationContext;
}
@Override
public Class<?> getObjectType() {
return SimpleEvaluationContext.class;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -19,6 +19,7 @@ package org.springframework.integration.context;
import java.util.Properties;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.config.IntegrationConfigUtils;
import org.springframework.integration.metadata.MetadataStore;
@@ -48,6 +49,8 @@ public abstract class IntegrationContextUtils {
public static final String INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME = "integrationEvaluationContext";
public static final String INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME = "integrationSimpleEvaluationContext";
public static final String INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME = "integrationHeaderChannelRegistry";
public static final String INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME = "integrationGlobalProperties";
@@ -120,12 +123,24 @@ public abstract class IntegrationContextUtils {
/**
* @param beanFactory BeanFactory for lookup, must not be null.
* @return the instance of {@link StandardEvaluationContext} bean whose name is "integrationEvaluationContext" .
* @return the instance of {@link StandardEvaluationContext} bean whose name is
* {@value #INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME}.
*/
public static StandardEvaluationContext getEvaluationContext(BeanFactory beanFactory) {
return getBeanOfType(beanFactory, INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME, StandardEvaluationContext.class);
}
/**
* @param beanFactory BeanFactory for lookup, must not be null.
* @return the instance of {@link SimpleEvaluationContext} bean whose name is
* {@value #INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME}.
* @since 4.3.15
*/
public static SimpleEvaluationContext getSimpleEvaluationContext(BeanFactory beanFactory) {
return getBeanOfType(beanFactory, INTEGRATION_SIMPLE_EVALUATION_CONTEXT_BEAN_NAME,
SimpleEvaluationContext.class);
}
private static <T> T getBeanOfType(BeanFactory beanFactory, String beanName, Class<T> type) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
if (!beanFactory.containsBean(beanName)) {
@@ -149,7 +164,8 @@ public abstract class IntegrationContextUtils {
Properties properties = new Properties();
properties.putAll(IntegrationProperties.defaults());
if (beanFactory != null) {
Properties userProperties = getBeanOfType(beanFactory, INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME, Properties.class);
Properties userProperties =
getBeanOfType(beanFactory, INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME, Properties.class);
if (userProperties != null) {
properties.putAll(userProperties);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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,18 +16,22 @@
package org.springframework.integration.expression;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.DataBindingPropertyAccessor;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.SimpleEvaluationContext.Builder;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility class with static methods for helping with establishing environments for
* SpEL expressions.
@@ -40,32 +44,22 @@ import org.apache.commons.logging.LogFactory;
public abstract class ExpressionUtils {
private static final Log logger = LogFactory.getLog(ExpressionUtils.class);
/**
* Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its
* property accessor property and the supplied {@link ConversionService} in its
* conversionService property.
* @param conversionService the conversion service.
* @return the evaluation context.
*/
private static StandardEvaluationContext createStandardEvaluationContext(ConversionService conversionService,
BeanFactory beanFactory) {
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.addPropertyAccessor(new MapAccessor());
if (conversionService != null) {
evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService));
}
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
/**
* Used to create a context with no BeanFactory, usually in tests.
* @return The evaluation context.
*/
public static StandardEvaluationContext createStandardEvaluationContext() {
return doCreateContext(null);
return (StandardEvaluationContext) doCreateContext(null, false);
}
/**
* Used to create a context with no BeanFactory, usually in tests.
* @return The evaluation context.
* @since 4.3.15
*/
public static SimpleEvaluationContext createSimpleEvaluationContext() {
return (SimpleEvaluationContext) doCreateContext(null, true);
}
/**
@@ -78,22 +72,73 @@ public abstract class ExpressionUtils {
if (beanFactory == null) {
logger.warn("Creating EvaluationContext with no beanFactory", new RuntimeException("No beanFactory"));
}
return doCreateContext(beanFactory);
return (StandardEvaluationContext) doCreateContext(beanFactory, false);
}
private static StandardEvaluationContext doCreateContext(BeanFactory beanFactory) {
/**
* Obtains the context from the beanFactory if not null; emits a warning if the beanFactory
* is null.
* @param beanFactory The bean factory.
* @return The evaluation context.
* @since 4.3.15
*/
public static SimpleEvaluationContext createSimpleEvaluationContext(BeanFactory beanFactory) {
if (beanFactory == null) {
logger.warn("Creating EvaluationContext with no beanFactory", new RuntimeException("No beanFactory"));
}
return (SimpleEvaluationContext) doCreateContext(beanFactory, true);
}
private static EvaluationContext doCreateContext(BeanFactory beanFactory, boolean simple) {
ConversionService conversionService = null;
StandardEvaluationContext evaluationContext = null;
EvaluationContext evaluationContext = null;
if (beanFactory != null) {
evaluationContext = IntegrationContextUtils.getEvaluationContext(beanFactory);
evaluationContext =
simple
? IntegrationContextUtils.getSimpleEvaluationContext(beanFactory)
: IntegrationContextUtils.getEvaluationContext(beanFactory);
}
if (evaluationContext == null) {
if (beanFactory != null) {
conversionService = IntegrationUtils.getConversionService(beanFactory);
}
evaluationContext = createStandardEvaluationContext(conversionService, beanFactory);
evaluationContext = createEvaluationContext(conversionService, beanFactory, simple);
}
return evaluationContext;
}
/**
* Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its
* property accessor property and the supplied {@link ConversionService} in its
* conversionService property.
* @param conversionService the conversion service.
* @param beanFactory the bean factory.
* @param simple true if simple.
* @return the evaluation context.
*/
private static EvaluationContext createEvaluationContext(ConversionService conversionService,
BeanFactory beanFactory, boolean simple) {
if (simple) {
Builder ecBuilder = SimpleEvaluationContext.forPropertyAccessors(
new MapAccessor(), DataBindingPropertyAccessor.forReadOnlyAccess())
.withInstanceMethods();
if (conversionService != null) {
ecBuilder.withConversionService(conversionService);
}
return ecBuilder.build();
}
else {
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.addPropertyAccessor(new MapAccessor());
if (conversionService != null) {
evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService));
}
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
}
}

View File

@@ -75,6 +75,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.BridgeFrom;
import org.springframework.integration.annotation.BridgeTo;
@@ -695,7 +696,7 @@ public class EnableIntegrationTests {
@Test
public void testIntegrationEvaluationContextCustomization() {
EvaluationContext evaluationContext = this.context.getBean(EvaluationContext.class);
EvaluationContext evaluationContext = this.context.getBean(StandardEvaluationContext.class);
List<?> propertyAccessors = TestUtils.getPropertyValue(evaluationContext, "propertyAccessors", List.class);
assertEquals(4, propertyAccessors.size());
assertThat(propertyAccessors.get(0), instanceOf(JsonPropertyAccessor.class));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -76,6 +76,7 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "charset");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "trusted-spel");
HttpAdapterParsingUtils.setExpectedResponseOrExpression(element, parserContext, builder);
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, parserContext, element);
return builder.getBeanDefinition();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -82,6 +82,7 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "charset");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-request-payload", "extractPayload");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "trusted-spel");
HttpAdapterParsingUtils.setExpectedResponseOrExpression(element, parserContext, builder);

View File

@@ -30,8 +30,10 @@ import javax.xml.transform.Source;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@@ -87,7 +89,11 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private final RestTemplate restTemplate;
private volatile StandardEvaluationContext evaluationContext;
private StandardEvaluationContext evaluationContext;
private SimpleEvaluationContext simpleEvaluationContext;
private boolean trustedSpel;
private final Expression uriExpression;
@@ -112,7 +118,6 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private volatile Expression uriVariablesExpression;
/**
* Create a handler that will send requests to the provided URI.
*
@@ -338,6 +343,17 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
this.transferCookies = transferCookies;
}
/**
* Set to true if you trust the source of SpEL expressions used to evaluate URI
* variables. Default is false, which means a {@link SimpleEvaluationContext} is used
* for evaluating such expressions, which restricts the use of some SpEL capabilities.
* @param trustedSpel true to trust.
* @since 4.3.15.
*/
public void setTrustedSpel(boolean trustedSpel) {
this.trustedSpel = trustedSpel;
}
@Override
public String getComponentType() {
return (this.expectReply ? "http:outbound-gateway" : "http:outbound-channel-adapter");
@@ -346,6 +362,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
@Override
protected void doInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
this.simpleEvaluationContext = ExpressionUtils.createSimpleEvaluationContext(this.getBeanFactory());
}
@Override
@@ -562,7 +579,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
* If all keys and values are Strings, we'll consider the Map to be form data.
*/
private boolean isFormData(Map<Object, ?> map) {
for (Object key : map.keySet()) {
for (Object key : map.keySet()) {
if (!(key instanceof String)) {
return false;
}
@@ -599,7 +616,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
|| expectedResponseType instanceof String
|| expectedResponseType instanceof ParameterizedTypeReference,
"'expectedResponseType' can be an instance of 'Class<?>', 'String' or 'ParameterizedTypeReference<?>'; "
+ "evaluation resulted in a" + expectedResponseType.getClass() + ".");
+ "evaluation resulted in a" + expectedResponseType.getClass() + ".");
if (expectedResponseType instanceof String && StringUtils.hasText((String) expectedResponseType)) {
expectedResponseType = ClassUtils.forName((String) expectedResponseType,
getApplicationContext().getClassLoader());
@@ -612,20 +629,24 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private Map<String, ?> determineUriVariables(Message<?> requestMessage) {
Map<String, ?> expressions;
EvaluationContext evaluationContextToUse = this.evaluationContext;
if (this.uriVariablesExpression != null) {
Object expressionsObject = this.uriVariablesExpression.getValue(this.evaluationContext, requestMessage);
Assert.state(expressionsObject instanceof Map,
"The 'uriVariablesExpression' evaluation must result in a 'Map'.");
expressions = (Map<String, ?>) expressionsObject;
if (!this.trustedSpel) {
evaluationContextToUse = this.simpleEvaluationContext;
}
}
else {
expressions = this.uriVariableExpressions;
}
return ExpressionEvalMap.from(expressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
.usingEvaluationContext(evaluationContextToUse)
.withRoot(requestMessage)
.build();
}

View File

@@ -397,6 +397,18 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="trusted-spel">
<xsd:annotation>
<xsd:documentation>
Set to 'true' if you trust SpEL expressions that might be evaluated to generate
URI variables.
The default value is 'false'.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
@@ -487,6 +499,18 @@
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
<xsd:attribute name="trusted-spel">
<xsd:annotation>
<xsd:documentation>
Set to 'true' if you trust SpEL expressions that might be evaluated to generate
URI variables.
The default value is 'false'.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -8,8 +8,9 @@
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<si:chain input-channel="httpOutboundChannelAdapterWithinChain">
<outbound-channel-adapter url="http://localhost/test1/%2f" encode-uri="false" rest-template="restTemplate"/>
<si:chain id="chain" input-channel="httpOutboundChannelAdapterWithinChain">
<outbound-channel-adapter id="adapter" url="http://localhost/test1/%2f" encode-uri="false" rest-template="restTemplate"
trusted-spel="true" />
</si:chain>
<beans:bean id="restTemplate" class="org.mockito.Mockito" factory-method="spy">
@@ -22,6 +23,7 @@
<outbound-gateway url="http://localhost:51235/%2f/testApps?param={param}"
rest-template="restTemplate"
encode-uri="false"
trusted-spel="true"
expected-response-type-expression="T (org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandlerTests).testParameterizedTypeReference()">
<uri-variable name="param" expression="T(java.net.URLEncoder).encode('http Outbound Gateway Within Chain', 'UTF-8')"/>
</outbound-gateway>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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,9 +16,11 @@
package org.springframework.integration.http.outbound;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
@@ -708,6 +710,9 @@ public class HttpRequestExecutingMessageHandlerTests {
channel.send(MessageBuilder.withPayload("test").build());
Mockito.verify(restTemplate).exchange(Mockito.eq(new URI("http://localhost/test1/%2f")),
Mockito.eq(HttpMethod.POST), Mockito.any(HttpEntity.class), Mockito.<Class<Object>>eq(null));
HttpRequestExecutingMessageHandler handler = ctx.getBean("chain$child.adapter.handler",
HttpRequestExecutingMessageHandler.class);
assertThat(TestUtils.getPropertyValue(handler, "trustedSpel"), equalTo(Boolean.TRUE));
ctx.close();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -134,13 +134,15 @@ public class UriVariableExpressionTests {
handler.setUriVariablesExpression(new SpelExpressionParser().parseExpression("headers.uriVariables"));
handler.afterPropertiesSet();
Map<String, String> expressions = new HashMap<String, String>();
Map<String, Object> expressions = new HashMap<String, Object>();
expressions.put("foo", "bar");
Map<String, ?> expressionsMap = ExpressionEvalMap.from(expressions).usingSimpleCallback().build();
try {
handler.handleMessage(MessageBuilder.withPayload("test").setHeader("uriVariables", expressionsMap).build());
handler.handleMessage(MessageBuilder.withPayload("test")
.setHeader("uriVariables", expressionsMap)
.build());
fail("Exception expected.");
}
catch (Exception e) {
@@ -148,6 +150,42 @@ public class UriVariableExpressionTests {
}
assertEquals("http://test/bar", uriHolder.get().toString());
expressions.put("foo", new SpelExpressionParser().parseExpression("'bar'.toUpperCase()"));
try {
handler.handleMessage(MessageBuilder.withPayload("test")
.setHeader("uriVariables", expressions)
.build());
fail("Exception expected.");
}
catch (Exception e) {
assertEquals("intentional", e.getCause().getMessage());
}
assertEquals("http://test/BAR", uriHolder.get().toString());
expressions.put("foo", new SpelExpressionParser().parseExpression("T(Integer).valueOf('42')"));
try {
handler.handleMessage(MessageBuilder.withPayload("test")
.setHeader("uriVariables", expressions)
.build());
fail("Exception expected.");
}
catch (Exception e) {
assertThat(e.getCause().getMessage(), containsString("Type cannot be found"));
}
handler.setTrustedSpel(true);
try {
handler.handleMessage(MessageBuilder.withPayload("test")
.setHeader("uriVariables", expressions)
.build());
fail("Exception expected.");
}
catch (Exception e) {
assertEquals("intentional", e.getCause().getMessage());
}
assertEquals("http://test/42", uriHolder.get().toString());
}
}

View File

@@ -523,6 +523,7 @@ Changes in Spring 3.1 can cause some issues with escaped characters, such as '?'
For this reason, it is recommended that if you wish to generate the URL entirely at runtime, you use the 'url-expression' attribute.
=====
[[mapping-uri-variables]]
==== Mapping URI Variables
If your URL contains URI variables, you can map them using the `uri-variable` sub-element.
@@ -581,6 +582,53 @@ NOTE: The `uri-variables-expression` must evaluate to a `Map`.
The values of the Map must be instances of `String` or `Expression`.
This Map is provided to an `ExpressionEvalMap` for further resolution of URI variable placeholders using those expressions in the context of the outbound `Message`.
IMPORTANT
====
The `uriVariablesExpression` property provides a very powerful mechanism for evaluating URI variables.
It is anticipated that simple expressions like the example above will be used.
However, you could also configure something like this `"@uriVariablesBean.populate(#root)"` with an expression in the returned map being `variables.put("foo", EXPRESSION_PARSER.parseExpression(message.getHeaders().get("bar", String.class)));`, where the expression is dynamically provided in the message header `bar`.
Since the header may come from an untrusted source, the HTTP outbound endpoints use a `SimpleEvaluationContext` when evaluating these expressions; allowing only a subset of SpEL features to be used.
If you trust your message sources and wish to use the restricted SpEL constructs, set the `trustedSpel` property of the outbound endpoint to `true`.
====
Scenarios when we need to supply a dynamic set of URI variables on per message basis can be achieved with the custom `url-expression` and some utilities for building and encoding URL parameters:
[source,xml]
----
url-expression="T(org.springframework.web.util.UriComponentsBuilder)
.fromHttpUrl('http://HOST:PORT/PATH')
.queryParams(payload)
.build()
.toUri()"
----
where `queryParams()` expects a `MultiValueMap<String, String>` as an argument, so a real set of URL query parameters can be build in advance, before performing request.
The whole `queryString` can also be presented as an uri variable:
[source,xml]
----
<int-http:outbound-gateway id="proxyGateway" request-channel="testChannel"
url="http://testServer/test?{queryString}">
<int-http:uri-variable name="queryString" expression="'a=A&amp;b=B'"/>
</int-http:outbound-gateway>
----
In this case the URL encoding must be provided manually.
For example the `org.apache.http.client.utils.URLEncodedUtils#format()` can be used for this purpose.
A mentioned, manually built, `MultiValueMap<String, String>` can be converted to the the `List<NameValuePair>` `format()` method argument using this Java Streams snippet:
[source,java]
----
List<NameValuePair> nameValuePairs =
params.entrySet()
.stream()
.flatMap(e -> e
.getValue()
.stream()
.map(v -> new BasicNameValuePair(e.getKey(), v)))
.collect(Collectors.toList());
----
==== Controlling URI Encoding
By default, the URL string is encoded (see http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/web/util/UriComponentsBuilder.html[UriComponentsBuilder]) to the URI object before sending the request.