Merge pull request #706 from garyrussell/INT-2864

* garyrussell-INT-2864:
  INT-2864 Enforce Ref/Expression Mutual Exclusivity
This commit is contained in:
Gunnar Hillert
2013-01-18 12:48:01 -05:00
16 changed files with 293 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-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.
@@ -20,9 +20,9 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.expression.DynamicExpression;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
@@ -30,9 +30,10 @@ import org.w3c.dom.Element;
* expression evaluator when handling consumed Messages. These classes
* use a FactoryBean implementation to construct the actual endpoint
* instance.
*
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
*/
abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumerEndpointParser {
@@ -50,7 +51,8 @@ abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumer
if (innerDefinition != null) {
if (hasRef || hasExpression || expressionElement != null) {
parserContext.getReaderContext().error(
"Neither 'ref' nor 'expression' are permitted when an inner bean (<bean/>) is configured.", source);
"Neither 'ref' nor 'expression' are permitted when an inner bean (<bean/>) is configured on element " +
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
return null;
}
builder.addPropertyValue("targetObject", innerDefinition);
@@ -58,7 +60,8 @@ abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumer
else if (scriptElement != null) {
if (hasRef || hasExpression || expressionElement != null) {
parserContext.getReaderContext().error(
"Neither 'ref' nor 'expression' are permitted when an inner script element is configured.", source);
"Neither 'ref' nor 'expression' are permitted when an inner script element is configured on element " +
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
return null;
}
BeanDefinition scriptBeanDefinition = parserContext.getDelegate().parseCustomElement(scriptElement, builder.getBeanDefinition());
@@ -67,17 +70,24 @@ abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumer
else if (expressionElement != null) {
if (hasRef || hasExpression) {
parserContext.getReaderContext().error(
"Neither 'ref' nor 'expression' are permitted when an inner 'expression' element is configured.", source);
"Neither 'ref' nor 'expression' are permitted when an inner 'expression' element is configured on element " +
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
return null;
}
BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.expression.DynamicExpression");
DynamicExpression.class);
String key = expressionElement.getAttribute("key");
String expressionSourceReference = expressionElement.getAttribute("source");
dynamicExpressionBuilder.addConstructorArgValue(key);
dynamicExpressionBuilder.addConstructorArgReference(expressionSourceReference);
builder.addPropertyValue("expression", dynamicExpressionBuilder.getBeanDefinition());
}
else if (hasRef && hasExpression) {
parserContext.getReaderContext().error(
"Only one of 'ref' or 'expression' is permitted, not both, on element " +
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
return null;
}
else if (hasRef) {
builder.addPropertyReference("targetObject", ref);
}
@@ -87,21 +97,23 @@ abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumer
else if (!this.hasDefaultOption()) {
parserContext.getReaderContext().error("Exactly one of the 'ref' attribute, 'expression' attribute, " +
"or inner bean (<bean/>) definition is required for element " +
IntegrationNamespaceUtils.createElementDescription(element) + ".", element);
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
return null;
}
String method = element.getAttribute(METHOD_ATTRIBUTE);
if (StringUtils.hasText(method)) {
if (hasExpression) {
if (hasExpression || expressionElement != null) {
parserContext.getReaderContext().error(
"A 'method' attribute is not permitted when configuring an 'expression'.", element);
"A 'method' attribute is not permitted when configuring an 'expression' on element " +
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
}
if (hasRef || innerDefinition != null) {
builder.addPropertyValue("targetMethodName", method);
}
else {
parserContext.getReaderContext().error("A 'method' attribute is only permitted when either " +
"a 'ref' or inner-bean definition is provided.", element);
"a 'ref' or inner-bean definition is provided on element " +
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
}
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "requires-reply");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-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.
@@ -18,25 +18,26 @@ package org.springframework.integration.config.xml;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.RouterFactoryBean;
import org.springframework.util.CollectionUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Parser for the &lt;router/&gt; element.
*
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
*/
public class DefaultRouterParser extends AbstractDelegatingConsumerEndpointParser {
@Override
String getFactoryBeanClassName() {
return IntegrationNamespaceUtils.BASE_PACKAGE + ".config.RouterFactoryBean";
return RouterFactoryBean.class.getName();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-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.
@@ -16,21 +16,22 @@
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.FilterFactoryBean;
import org.w3c.dom.Element;
/**
* Parser for the &lt;filter/&gt; element.
*
*
* @author Mark Fisher
* @author Gary Russell
*/
public class FilterParser extends AbstractDelegatingConsumerEndpointParser {
@Override
String getFactoryBeanClassName() {
return IntegrationNamespaceUtils.BASE_PACKAGE + ".config.FilterFactoryBean";
return FilterFactoryBean.class.getName();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-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
@@ -285,10 +285,13 @@ public abstract class IntegrationNamespaceUtils {
innerComponentDefinition = new BeanComponentDefinition(inDef, bdHolder.getBeanName());
}
String ref = element.getAttribute(REF_ATTRIBUTE);
Assert.isTrue(!(StringUtils.hasText(ref) && innerComponentDefinition != null),
if (StringUtils.hasText(ref) && innerComponentDefinition != null) {
parserContext.getReaderContext().error(
"Ambiguous definition. Inner bean " + (innerComponentDefinition == null ? innerComponentDefinition
: innerComponentDefinition.getBeanDefinition().getBeanClassName())
+ " declaration and \"ref\" " + ref + " are not allowed together.");
+ " declaration and \"ref\" " + ref + " are not allowed together on element " +
IntegrationNamespaceUtils.createElementDescription(element) + ".", parserContext.extractSource(element));
}
return innerComponentDefinition;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-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.
@@ -16,17 +16,20 @@
package org.springframework.integration.config.xml;
import org.springframework.integration.config.ServiceActivatorFactoryBean;
/**
* Parser for the &lt;service-activator&gt; element.
*
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
*/
public class ServiceActivatorParser extends AbstractDelegatingConsumerEndpointParser {
@Override
String getFactoryBeanClassName() {
return "org.springframework.integration.config.ServiceActivatorFactoryBean";
return ServiceActivatorFactoryBean.class.getName();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-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.
@@ -18,19 +18,21 @@ package org.springframework.integration.config.xml;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.SplitterFactoryBean;
import org.w3c.dom.Element;
/**
* Parser for the &lt;splitter/&gt; element.
*
*
* @author Mark Fisher
* @author Iwein Fuld
* @author Gary Russell
*/
public class SplitterParser extends AbstractDelegatingConsumerEndpointParser {
@Override
String getFactoryBeanClassName() {
return IntegrationNamespaceUtils.BASE_PACKAGE + ".config.SplitterFactoryBean";
return SplitterFactoryBean.class.getName();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-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.
@@ -16,16 +16,19 @@
package org.springframework.integration.config.xml;
import org.springframework.integration.config.TransformerFactoryBean;
/**
* Parser for the &lt;transformer/&gt; element.
*
*
* @author Mark Fisher
* @author Gary Russell
*/
public class TransformerParser extends AbstractDelegatingConsumerEndpointParser {
@Override
String getFactoryBeanClassName() {
return IntegrationNamespaceUtils.BASE_PACKAGE + ".config.TransformerFactoryBean";
return TransformerFactoryBean.class.getName();
}
@Override

View File

@@ -0,0 +1,14 @@
<?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"
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">
<service-activator id="test" input-channel="literalExpressionInput" expression="'foo'">
<beans:bean id="testBean" class="org.springframework.integration.config.xml.ServiceActivatorParserTests$TestBean"/>
</service-activator>
</beans:beans>

View File

@@ -0,0 +1,16 @@
<?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"
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">
<service-activator id="test" input-channel="literalExpressionInput" expression="'foo'">
<expression key="foo" source="expressionSource" />
</service-activator>
<beans:bean id="expressionSource" class="org.springframework.integration.expression.ReloadableResourceBundleExpressionSource" />
</beans:beans>

View File

@@ -0,0 +1,16 @@
<?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"
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">
<service-activator id="test" input-channel="literalExpressionInput" method="foo">
<expression key="foo" source="expressionSource" />
</service-activator>
<beans:bean id="expressionSource" class="org.springframework.integration.expression.ReloadableResourceBundleExpressionSource" />
</beans:beans>

View File

@@ -0,0 +1,12 @@
<?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"
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">
<service-activator id="test" input-channel="literalExpressionInput" />
</beans:beans>

View File

@@ -0,0 +1,14 @@
<?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"
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">
<service-activator id="test" input-channel="literalExpressionInput" ref="testBean">
<beans:bean id="testBean" class="org.springframework.integration.config.xml.ServiceActivatorParserTests$TestBean"/>
</service-activator>
</beans:beans>

View File

@@ -0,0 +1,14 @@
<?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"
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">
<service-activator id="test" input-channel="literalExpressionInput" expression="@testBean.caps(payload)" ref="testBean" />
<beans:bean id="testBean" class="org.springframework.integration.config.xml.ServiceActivatorParserTests$TestBean"/>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-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.
@@ -17,11 +17,15 @@
package org.springframework.integration.config.xml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.core.MessagingTemplate;
@@ -112,6 +116,89 @@ public class ServiceActivatorParserTests {
assertEquals("bar", result);
}
@Test
public void failRefAndExpression() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-ref-and-expression-context.xml",
this.getClass());
fail("Expected exception");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: Only one of 'ref' or 'expression' is permitted, not both, " +
"on element 'service-activator' with id='test'."));
}
}
@Test
public void failRefAndBean() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-ref-and-bean-context.xml",
this.getClass());
fail("Expected exception");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: Ambiguous definition. " +
"Inner bean org.springframework.integration.config.xml.ServiceActivatorParserTests$TestBean " +
"declaration and \"ref\" testBean are not allowed together on element " +
"'service-activator' with id='test'."));
}
}
@Test
public void failExpressionAndBean() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-expression-and-bean-context.xml",
this.getClass());
fail("Expected exception");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: Neither 'ref' nor 'expression' " +
"are permitted when an inner bean (<bean/>) is configured on element " +
"'service-activator' with id='test'."));
}
}
@Test
public void failNoService() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-no-service-context.xml",
this.getClass());
fail("Expected exception");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: Exactly one of the 'ref' " +
"attribute, 'expression' attribute, or inner bean (<bean/>) definition " +
"is required for element 'service-activator' with id='test'."));
}
}
@Test
public void failExpressionAndExpression() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-expression-and-expression-element-context.xml",
this.getClass());
fail("Expected exception");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: Neither 'ref' nor 'expression' are permitted when " +
"an inner 'expression' element is configured on element " +
"'service-activator' with id='test'."));
}
}
@Test
public void failMethodAndExpressionElement() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-method-and-expression-element-context.xml",
this.getClass());
fail("Expected exception");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: A 'method' attribute is not permitted when configuring " +
"an 'expression' on element 'service-activator' with id='test'."));
}
}
private Object sendAndReceive(MessageChannel channel, Object payload) {
MessagingTemplate template = new MessagingTemplate(channel);
return template.convertSendAndReceive(payload);

View File

@@ -0,0 +1,16 @@
<?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:int-groovy="http://www.springframework.org/schema/integration/groovy"
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/groovy http://www.springframework.org/schema/integration/groovy/spring-integration-groovy.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<service-activator id="test" input-channel="literalExpressionInput" expression="'foo'">
<int-groovy:script>
payload
</int-groovy:script>
</service-activator>
</beans:beans>

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-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.groovy.config;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Gary Russell
* @since 2.2
*/
public class ServiceActivatorParserTests {
@Test
public void failExpressionAndScript() {
try {
new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-fail-expression-and-script-context.xml",
this.getClass());
fail("Expected exception");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: Neither 'ref' nor 'expression' are permitted when " +
"an inner script element is configured on element 'service-activator' with id='test'."));
}
}
}