INT-3139: Add #jsonPath SpEL Function Support

* #jsonPath function is registered automatically if the library is on the classpath
* Add otional dependency on `com.jayway.jsonpath:json-path` artifact
* Add `JsonPathUtils` class as a wrapper around `JsonPath`
* Add `#jsonPath` tests for: `<transformer>`, `<filter>`, `<splitter>`, `<router>`

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

INT-3139: Further improvements

* Remove `SpelFunctionRegistrar`
* Introduce `SpelFunctionFactoryBean`

INT-3139: Polishing
This commit is contained in:
Artem Bilan
2013-09-10 15:39:24 +03:00
committed by Gary Russell
parent 4d3bc36ff0
commit 63cbf736a2
16 changed files with 587 additions and 202 deletions

View File

@@ -185,6 +185,7 @@ project('spring-integration-core') {
compile "com.eaio.uuid:uuid:$eaioUUIDVersion"
compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional)
compile("com.fasterxml.jackson.core:jackson-databind:$jackson2Version", optional)
compile('com.jayway.jsonpath:json-path:0.8.1', optional)
testCompile "org.aspectj:aspectjweaver:$aspectjVersion"
}
}

View File

@@ -77,7 +77,7 @@ public class OutboundGatewayTests {
}).when(context).getBean(anyString());
when(context.containsBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)).thenReturn(true);
IntegrationEvaluationContextFactoryBean integrationEvaluationContextFactoryBean = new IntegrationEvaluationContextFactoryBean();
integrationEvaluationContextFactoryBean.setBeanFactory(context);
integrationEvaluationContextFactoryBean.setApplicationContext(context);
integrationEvaluationContextFactoryBean.afterPropertiesSet();
StandardEvaluationContext evalContext = integrationEvaluationContextFactoryBean.getObject();
when(context.getBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME, StandardEvaluationContext.class))

View File

@@ -25,11 +25,11 @@ import java.util.Map;
import java.util.Map.Entry;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
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;
@@ -66,7 +66,7 @@ import org.springframework.util.Assert;
* @since 3.0
*/
public class IntegrationEvaluationContextFactoryBean implements FactoryBean<StandardEvaluationContext>,
BeanFactoryAware, InitializingBean {
ApplicationContextAware, InitializingBean {
private volatile List<PropertyAccessor> propertyAccessors = new ArrayList<PropertyAccessor>();
@@ -74,14 +74,13 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean<Stan
private TypeConverter typeConverter = new StandardTypeConverter();
private BeanFactory beanFactory;
private ApplicationContext applicationContext;
private BeanResolver beanResolver;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void setPropertyAccessors(PropertyAccessor... accessors) {
@@ -101,35 +100,26 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean<Stan
this.functions = functions;
}
public void addFunction(String name, Method method) {
this.functions.put(name, method);
}
public void addFunctions(Map<String, Method> functions) {
this.functions.putAll(functions);
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.propertyAccessors.isEmpty()) {
this.loadDefaultPropertyAccessors(this.propertyAccessors);
}
if (this.beanFactory != null) {
this.beanResolver = new BeanFactoryResolver(this.beanFactory);
ConversionService conversionService = IntegrationContextUtils.getConversionService(this.beanFactory);
if (this.applicationContext != null) {
this.beanResolver = new BeanFactoryResolver(this.applicationContext);
ConversionService conversionService = IntegrationContextUtils.getConversionService(this.applicationContext);
if (conversionService != null) {
this.typeConverter = new StandardTypeConverter(conversionService);
}
try {
SpelFunctionRegistrar functionRegistrar = this.beanFactory.getBean(SpelFunctionRegistrar.class);
if (functionRegistrar != null) {
this.addFunctions(functionRegistrar.getFunctions());
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());
}
}
catch (NoSuchBeanDefinitionException e) {
//Ignore it.
//There is no <spel-function> components within application context.
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
/**
* A {@link FactoryBean} implementation to encapsulate the population of a static {@link Method}
* from the provided {@linkplain #functionClass} and {@linkplain #functionMethodSignature} as
* a valid {@link org.springframework.expression.spel.support.StandardEvaluationContext} function.
*
* @author Artem Bilan
* @since 3.0
*
* @see org.springframework.expression.spel.support.StandardEvaluationContext#registerFunction
* @see BeanUtils#resolveSignature
*/
public class SpelFunctionFactoryBean implements FactoryBean<Method>, InitializingBean, BeanNameAware {
private final Class<?> functionClass;
private final String functionMethodSignature;
private String functionName;
private Method method;
public SpelFunctionFactoryBean(Class<?> functionClass, String functionMethodSignature) {
this.functionClass = functionClass;
this.functionMethodSignature = functionMethodSignature;
}
@Override
public void setBeanName(String name) {
this.functionName = name;
}
public String getFunctionName() {
return functionName;
}
@Override
public void afterPropertiesSet() throws Exception {
this.method = BeanUtils.resolveSignature(this.functionMethodSignature, this.functionClass);
if (this.method == null) {
throw new BeanDefinitionStoreException(String.format("No declared method '%s' in class '%s'",
this.functionMethodSignature, this.functionClass));
}
if (!Modifier.isStatic(this.method.getModifiers())) {
throw new BeanDefinitionStoreException("SpEL-function method has to be 'static'");
}
}
@Override
public Method getObject() throws Exception {
return method;
}
@Override
public Class<?> getObjectType() {
return Method.class;
}
@Override
public boolean isSingleton() {
return true;
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.config;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* <p>
* Utility class that keeps track of a Map of SpEL functions in order to register
* them with the "integrationEvaluationContext" upon initialization.
* </p>
* <p>
* The {@link org.springframework.integration.config.xml.SpelFunctionParser}
* doesn't register a bean for each &lt;spel-function&gt; within application context.
* There is no automatic way to get 'functions' from a parent context
* and this class provide a hook to get 'functions' from the parent context.
* </p>
*
* @author Artem Bilan
*
* @since 3.0
*/
class SpelFunctionRegistrar implements ApplicationContextAware, InitializingBean {
private final Map<String, Method> functions;
private ApplicationContext applicationContext;
SpelFunctionRegistrar(Map<String, Method> functions) {
this.functions = functions;
}
Map<String, Method> getFunctions() {
return functions;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
ApplicationContext parent = this.applicationContext.getParent();
if (parent != null) {
try {
SpelFunctionRegistrar parentFunctionRegistrar = parent.getBean(SpelFunctionRegistrar.class);
Map<String, Method> parentFunctions = parentFunctionRegistrar.getFunctions();
for (String key : parentFunctions.keySet()) {
if(!this.functions.containsKey(key)) {
this.functions.put(key, parentFunctions.get(key));
}
}
}
catch (NoSuchBeanDefinitionException e) {
//Ignore it.
//There is no <spel-function> components within parent application context.
}
}
}
}

View File

@@ -18,6 +18,8 @@ package org.springframework.integration.config.xml;
import static org.springframework.integration.context.IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -37,6 +39,7 @@ import org.springframework.integration.config.IntegrationEvaluationContextFactor
import org.springframework.integration.config.xml.ChannelInitializer.AutoCreateCandidatesCollector;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.expression.IntegrationEvaluationContextAwareBeanPostProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@@ -49,6 +52,8 @@ import org.springframework.util.StringUtils;
*/
public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
private static final String VERSION = "3.0";
public static final String CHANNEL_INITIALIZER_BEAN_NAME = "channelInitializer";
@@ -67,6 +72,7 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
this.verifySchemaVersion(element, parserContext);
this.registerImplicitChannelCreator(parserContext);
this.registerIntegrationEvaluationContext(parserContext);
this.registerBuiltInBeans(parserContext);
this.registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(parserContext);
return this.delegate.parse(element, parserContext);
}
@@ -141,6 +147,37 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
}
}
private void registerBuiltInBeans(ParserContext parserContext) {
String jsonPathBeanName = "jsonPath";
boolean alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ListableBeanFactory) {
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBean(jsonPathBeanName);
}
else {
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(jsonPathBeanName);
}
if (!alreadyRegistered) {
Class<?> jsonPathClass = null;
try {
jsonPathClass = ClassUtils.forName("com.jayway.jsonpath.JsonPath", parserContext.getReaderContext().getBeanClassLoader());
}
catch (ClassNotFoundException e) {
logger.debug("SpEL function '#jsonPath' isn't registered: there is no jayway json-path.jar on the classpath.");
}
if (jsonPathClass != null) {
IntegrationNamespaceUtils.registerSpelFunctionBean(parserContext.getRegistry(), jsonPathBeanName,
IntegrationNamespaceUtils.BASE_PACKAGE + ".json.JsonPathUtils", "evaluate");
}
}
this.doRegisterBuiltInBeans(parserContext);
}
protected void doRegisterBuiltInBeans(ParserContext parserContext) {
}
private void registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(ParserContext parserContext) {
boolean alreadyRegistered = false;
if (parserContext.getRegistry() instanceof ListableBeanFactory) {
@@ -171,10 +208,10 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
}
private void verifySchemaVersion(Element element, ParserContext parserContext) {
if (!(matchesVersion(element) && matchesVersion(element.getOwnerDocument().getDocumentElement()))) {
if (!(matchesVersion(element) && matchesVersion(element.getOwnerDocument().getDocumentElement()))) {
parserContext.getReaderContext().error(
"You cannot use prior versions of Spring Integration schemas with Spring Integration " + VERSION +
". Please upgrade your schema declarations or use versionless aliases (e.g. spring-integration.xsd).", element);
"You cannot use prior versions of Spring Integration schemas with Spring Integration " + VERSION +
". Please upgrade your schema declarations or use versionless aliases (e.g. spring-integration.xsd).", element);
}
}

View File

@@ -17,12 +17,17 @@ import static org.springframework.beans.factory.xml.AbstractBeanDefinitionParser
import java.util.List;
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.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
@@ -30,6 +35,7 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.Conventions;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.config.SpelFunctionFactoryBean;
import org.springframework.integration.endpoint.AbstractPollingEndpoint;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource;
@@ -37,9 +43,6 @@ import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Shared utility methods for integration namespace parsers.
@@ -456,4 +459,14 @@ public abstract class IntegrationNamespaceUtils {
}
return expressionDef;
}
public static void registerSpelFunctionBean(BeanDefinitionRegistry registry, String functionId, String className,
String methodSignature) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpelFunctionFactoryBean.class)
.addConstructorArgValue(className)
.addConstructorArgValue(methodSignature);
registry.registerBeanDefinition(functionId, builder.getBeanDefinition());
}
}

View File

@@ -16,78 +16,29 @@
package org.springframework.integration.config.xml;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.ClassUtils;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.integration.config.SpelFunctionFactoryBean;
/**
* Parser for the &lt;spel-function&gt; element.
* Doesn't register a bean within application context, collects 'functions'
* within {@link org.springframework.integration.config.SpelFunctionRegistrar} bean.
*
* @author Artem Bilan
* @since 3.0
*/
public class SpelFunctionParser implements BeanDefinitionParser {
private final Map<String, Method> functions = new LinkedHashMap<String, Method>();
private volatile boolean initialized;
public class SpelFunctionParser extends AbstractSingleBeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
this.initializeSpelFunctionRegistrarIfNecessary(parserContext);
String id = element.getAttribute("id");
String className = element.getAttribute("class");
String signature = element.getAttribute("method");
Class<?> clazz = null;
try {
clazz = ClassUtils.forName(className, parserContext.getReaderContext().getBeanClassLoader());
}
catch (ClassNotFoundException e) {
parserContext.getReaderContext().error(e.getMessage(), element);
}
Method method = BeanUtils.resolveSignature(signature, clazz);
if (method == null) {
parserContext.getReaderContext().error(String.format("No declared method '%s' in class '%s'",
signature, className), element);
return null;
}
if (!Modifier.isStatic(method.getModifiers())) {
parserContext.getReaderContext().error("SpEL-function method has to be 'static'", element);
}
this.functions.put(id, method);
return null;
protected Class<?> getBeanClass(Element element) {
return SpelFunctionFactoryBean.class;
}
private synchronized void initializeSpelFunctionRegistrarIfNecessary(ParserContext parserContext) {
if (!this.initialized) {
BeanDefinitionBuilder registrarBuilder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE + ".config.SpelFunctionRegistrar")
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addConstructorArgValue(this.functions);
BeanDefinitionReaderUtils.registerWithGeneratedName(registrarBuilder.getBeanDefinition(),
parserContext.getRegistry());
this.initialized = true;
}
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
builder.addConstructorArgValue(element.getAttribute("class"))
.addConstructorArgValue(element.getAttribute("method"));
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.json;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import com.jayway.jsonpath.Filter;
import com.jayway.jsonpath.JsonPath;
/**
* Utility class to {@link #evaluate} a jsonPath on the provided object.
* Delegates evaluation to <a href="http://code.google.com/p/json-path">JsonPath</a>.
* Note {@link #evaluate} is used as {@code #jsonPath()} SpEL function.
*
* @author Artem Bilan
* @since 3.0
*/
public final class JsonPathUtils {
public static <T> T evaluate(Object json, String jsonPath, Filter<?>... filters) throws Exception {
if (json instanceof String) {
return JsonPath.read((String) json, jsonPath, filters);
}
else if (json instanceof File) {
return JsonPath.read((File) json, jsonPath, filters);
}
else if (json instanceof URL) {
return JsonPath.read((URL) json, jsonPath, filters);
}
else if (json instanceof InputStream) {
return JsonPath.read((InputStream) json, jsonPath, filters);
}
else {
return JsonPath.read(json, jsonPath, filters);
}
}
private JsonPathUtils() {
}
}

View File

@@ -47,7 +47,7 @@ public class PublisherExpressionTests {
public void setup() throws Exception {
context.registerSingleton("testChannel", QueueChannel.class);
IntegrationEvaluationContextFactoryBean factory = new IntegrationEvaluationContextFactoryBean();
factory.setBeanFactory(context);
factory.setApplicationContext(context);
factory.afterPropertiesSet();
EvaluationContext ec = factory.getObject();
context.getBeanFactory().registerSingleton(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME, ec);

View File

@@ -5,11 +5,11 @@
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">
<bean id="bar" class="java.lang.String">
<bean id="barString" class="java.lang.String">
<constructor-arg value="bar"/>
</bean>
<int:transformer input-channel="input" output-channel="output" expression="@foo + @bar"/>
<int:transformer input-channel="input" output-channel="output" expression="@foo + @barString"/>
<int:channel id="output">
<int:queue/>
@@ -21,4 +21,6 @@
<int:spel-function id="barChild" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
<int:spel-function id="jsonPath" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
</beans>

View File

@@ -19,22 +19,26 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.hamcrest.Matchers;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
import org.springframework.integration.json.JsonPathUtils;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
@@ -56,34 +60,34 @@ public class ParentContextTests {
* and parent contexts work.
*/
@Test
public void testSpelBeanReferencesInChildAndParent() throws ClassNotFoundException {
//To check if 'org.springframework.integration.config.SpelFunctionRegistrar#afterPropertiesSet()'
//doesn't throw any Exception
AbstractApplicationContext superParent = new GenericApplicationContext();
superParent.refresh();
public void testSpelBeanReferencesInChildAndParent() throws Exception {
AbstractApplicationContext parent = new ClassPathXmlApplicationContext("ParentContext-context.xml", this.getClass());
AbstractApplicationContext parent = new ClassPathXmlApplicationContext(new String[]{"ParentContext-context.xml"},
this.getClass(), superParent);
Class<?> spelFunctionRegistrarClass = Class.forName("org.springframework.integration.config.SpelFunctionRegistrar");
Object parentSpelFunctionRegistrar = parent.getBean(spelFunctionRegistrarClass);
assertEquals(2, TestUtils.getPropertyValue(parentSpelFunctionRegistrar, "functions", Map.class).size());
Object parentEvaluationContextFactoryBean = parent.getBean(IntegrationEvaluationContextFactoryBean.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 childSpelFunctionRegistrar = child.getBean(spelFunctionRegistrarClass);
Map functions = TestUtils.getPropertyValue(childSpelFunctionRegistrar, "functions", Map.class);
assertEquals(3, functions.size());
assertTrue(functions.containsKey("barParent"));
Object childEvaluationContextFactoryBean = child.getBean(IntegrationEvaluationContextFactoryBean.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());
assertNotSame(evalContexts.get(1).getBeanResolver(), evalContexts.get(2).getBeanResolver());
assertSame(parent.getBeanFactory(), TestUtils.getPropertyValue(evalContexts.get(0).getBeanResolver(), "beanFactory"));
assertSame(child.getBeanFactory(), TestUtils.getPropertyValue(evalContexts.get(2).getBeanResolver(), "beanFactory"));
assertSame(parent, TestUtils.getPropertyValue(evalContexts.get(0).getBeanResolver(), "beanFactory"));
assertSame(child, TestUtils.getPropertyValue(evalContexts.get(2).getBeanResolver(), "beanFactory"));
// Test transformer expressions
child.getBean("input", MessageChannel.class).send(new GenericMessage<String>("baz"));

View File

@@ -29,8 +29,8 @@ import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.Resource;
@@ -52,6 +52,7 @@ import org.springframework.integration.test.util.TestUtils;
* @author Mark Fisher
* @author Gunnar Hillert
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class ExpressionEvaluatingMessageProcessorTests {
@@ -121,11 +122,11 @@ public class ExpressionEvaluatingMessageProcessorTests {
}
Expression expression = expressionParser.parseExpression("#target.find(payload)");
ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression);
ConfigurableListableBeanFactory beanFactory = new GenericApplicationContext().getBeanFactory();
processor.setBeanFactory(beanFactory);
AbstractApplicationContext applicationContext = new GenericApplicationContext();
processor.setBeanFactory(applicationContext);
IntegrationEvaluationContextFactoryBean factoryBean = new IntegrationEvaluationContextFactoryBean();
factoryBean.setBeanFactory(beanFactory);
beanFactory.registerSingleton(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME,
factoryBean.setApplicationContext(applicationContext);
applicationContext.getBeanFactory().registerSingleton(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME,
factoryBean.getObject());
processor.afterPropertiesSet();
EvaluationContext evaluationContext = TestUtils.getPropertyValue(processor, "evaluationContext", EvaluationContext.class);
@@ -173,6 +174,8 @@ public class ExpressionEvaluatingMessageProcessorTests {
context.registerBeanDefinition("testString", beanDefinition);
context.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME,
new RootBeanDefinition(IntegrationEvaluationContextFactoryBean.class));
context.refresh();
Expression expression = expressionParser.parseExpression("payload.concat(@testString)");
ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression);
processor.setBeanFactory(context);
@@ -190,6 +193,8 @@ public class ExpressionEvaluatingMessageProcessorTests {
context.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME,
new RootBeanDefinition(IntegrationEvaluationContextFactoryBean.class));
context.registerBeanDefinition("testString", beanDefinition);
context.refresh();
Expression expression = expressionParser.parseExpression("@testString.concat(payload)");
ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression);
processor.setBeanFactory(context);

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.springframework.integration.json">
<context:include-filter type="regex" expression="JsonPathTests"/>
</context:component-scan>
<channel id="output">
<queue/>
</channel>
<transformer input-channel="transformerInput" output-channel="output"
expression="#jsonPath(payload, '$.store.book[0].author')"/>
<filter input-channel="filterInput1" output-channel="output" discard-channel="discardChannel"
throw-exception-on-rejection="true"
expression="!#jsonPath(payload, '$..bicycle').empty"/>
<channel id="discardChannel">
<queue/>
</channel>
<filter input-channel="filterInput2" output-channel="output"
expression="#jsonPath(payload, '$..book[2].isbn') matches '^\d-\d{3}-\d{5}-\d$'"/>
<filter input-channel="filterInput3" output-channel="output"
expression="#jsonPath(payload, '$..book[?(@.price > ' + headers.price + ')]').size() > 1"/>
<filter input-channel="filterInput4" output-channel="output"
expression="#jsonPath(payload, '$.store.book[?]', @jsonPathFilter).empty"/>
<splitter input-channel="splitterInput" output-channel="splitterOutput"
expression="#jsonPath(payload, '$.store.book')"/>
<channel id="splitterOutput">
<queue/>
</channel>
<router input-channel="routerInput" expression="#jsonPath(payload, headers.jsonPath)">
<mapping channel="routerOutput1" value="reference"/>
<mapping channel="routerOutput2" value="fiction"/>
</router>
<channel id="routerOutput1">
<queue/>
</channel>
<channel id="routerOutput2">
<queue/>
</channel>
</beans:beans>

View File

@@ -0,0 +1,217 @@
/*
* 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.json;
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 java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Scanner;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.hamcrest.Matchers;
import com.jayway.jsonpath.Criteria;
import com.jayway.jsonpath.Filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessageRejectedException;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
/**
* @author Artem Bilan
* @since 3.0
*/
@ContextConfiguration(classes = JsonPathTests.JsonPathTestsContextConfiguration.class, loader = AnnotationConfigContextLoader.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JsonPathTests {
private static File JSON_FILE;
private static String JSON;
private static Message<String> testMessage;
@BeforeClass
public static void setUp() throws IOException {
ClassPathResource jsonResource = new ClassPathResource("JsonPathTests.json", JsonPathTests.class);
JSON_FILE = jsonResource.getFile();
JSON = new Scanner(JSON_FILE).useDelimiter("\\Z").next();
testMessage = new GenericMessage<String>(JSON);
}
@Autowired
private PollableChannel output;
@Autowired
private volatile MessageChannel transformerInput;
@Autowired
private volatile MessageChannel filterInput1;
@Autowired
private PollableChannel discardChannel;
@Autowired
private volatile MessageChannel filterInput2;
@Autowired
private volatile MessageChannel filterInput3;
@Autowired
private volatile MessageChannel filterInput4;
@Autowired
private volatile MessageChannel splitterInput;
@Autowired
private PollableChannel splitterOutput;
@Autowired
private volatile MessageChannel routerInput;
@Autowired
private PollableChannel routerOutput1;
@Autowired
private PollableChannel routerOutput2;
@Test
public void testInt3139JsonPathTransformer() throws IOException {
this.transformerInput.send(testMessage);
Message<?> receive = this.output.receive(1000);
assertNotNull(receive);
assertEquals("Nigel Rees", receive.getPayload());
this.transformerInput.send(new GenericMessage<File>(JSON_FILE));
receive = this.output.receive(1000);
assertNotNull(receive);
assertEquals("Nigel Rees", receive.getPayload());
try {
this.transformerInput.send(new GenericMessage<Object>(new Object()));
fail("IllegalArgumentException expected");
}
catch (Exception e) {
//MessageTransformationException / MessageHandlingException / InvocationTargetException / IllegalArgumentException
Throwable cause = e.getCause().getCause().getCause();
assertTrue(cause instanceof IllegalArgumentException);
assertEquals("Invalid container object", cause.getMessage());
}
}
@Test
public void testInt3139JsonPathFilter() {
this.filterInput1.send(testMessage);
Message<?> receive = this.output.receive(1000);
assertNotNull(receive);
assertEquals(JSON, receive.getPayload());
this.filterInput2.send(testMessage);
receive = this.output.receive(1000);
assertNotNull(receive);
Message<String> message = MessageBuilder.withPayload(JSON)
.setHeader("price", 10)
.build();
this.filterInput3.send(message);
receive = this.output.receive(1000);
assertNotNull(receive);
this.filterInput4.send(testMessage);
receive = this.output.receive(1000);
assertNotNull(receive);
try {
this.filterInput1.send(new GenericMessage<String>("{foo:{}}"));
fail("MessageRejectedException is expected.");
}
catch (Exception e) {
assertThat(e, Matchers.instanceOf(MessageRejectedException.class));
}
receive = this.output.receive(0);
assertNull(receive);
receive = this.discardChannel.receive(1000);
assertNotNull(receive);
}
@Test
public void testInt3139JsonPathSplitter() {
this.splitterInput.send(testMessage);
for(int i = 0; i < 3; i++) {
Message<?> receive = this.splitterOutput.receive(1000);
assertNotNull(receive);
assertTrue(receive.getPayload() instanceof Map);
}
}
@Test
public void testInt3139JsonPathRouter() {
Message<String> message = MessageBuilder.withPayload(JSON)
.setHeader("jsonPath", "$.store.book[0].category")
.build();
this.routerInput.send(message);
Message<?> receive = this.routerOutput1.receive(1000);
assertNotNull(receive);
assertEquals(JSON, receive.getPayload());
assertNull(this.routerOutput2.receive(10));
message = MessageBuilder.withPayload(JSON)
.setHeader("jsonPath", "$.store.book[2].category")
.build();
this.routerInput.send(message);
receive = this.routerOutput2.receive(1000);
assertNotNull(receive);
assertEquals(JSON, receive.getPayload());
assertNull(this.routerOutput1.receive(10));
}
@Configuration
@ImportResource("classpath:org/springframework/integration/json/JsonPathTests-context.xml")
public static class JsonPathTestsContextConfiguration {
@Bean
public Filter jsonPathFilter() {
return Filter.filter(Criteria.where("isbn").exists(true).and("category").ne("fiction"));
}
}
}

View File

@@ -0,0 +1,35 @@
{ "store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}