INT-2867: Add Script Support to inbound-c-a
* Add `<script>` and `<expression>` to `<int:inbound-channel-adapter>` * Refactoring for `DefaultInboundChannelAdapterParser` * Introduce `ScriptExecutingMessageSource` to allow scripts' `MessageProcessor` implementations work as a source for polling endpoint JIRA: https://jira.springsource.org/browse/INT-2867 INT-2867: documentation In addition the tests for Lifecycle attributes Polishing
This commit is contained in:
committed by
Gary Russell
parent
63cbf736a2
commit
09be87551b
@@ -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.
|
||||
@@ -66,6 +66,8 @@ public class ConsumerEndpointFactoryBean
|
||||
|
||||
private volatile boolean autoStartup = true;
|
||||
|
||||
private volatile int phase = 0;
|
||||
|
||||
private volatile MessageChannel inputChannel;
|
||||
|
||||
private volatile ConfigurableBeanFactory beanFactory;
|
||||
@@ -112,6 +114,10 @@ public class ConsumerEndpointFactoryBean
|
||||
this.autoStartup = autoStartup;
|
||||
}
|
||||
|
||||
public void setPhase(int phase) {
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
@@ -239,6 +245,7 @@ public class ConsumerEndpointFactoryBean
|
||||
this.endpoint.setBeanName(this.beanName);
|
||||
this.endpoint.setBeanFactory(this.beanFactory);
|
||||
this.endpoint.setAutoStartup(this.autoStartup);
|
||||
this.endpoint.setPhase(this.phase);
|
||||
this.endpoint.afterPropertiesSet();
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -48,6 +48,8 @@ public class SourcePollingChannelAdapterFactoryBean implements FactoryBean<Sourc
|
||||
|
||||
private volatile boolean autoStartup = true;
|
||||
|
||||
private volatile int phase = 0;
|
||||
|
||||
private volatile Long sendTimeout;
|
||||
|
||||
private volatile String beanName;
|
||||
@@ -82,6 +84,10 @@ public class SourcePollingChannelAdapterFactoryBean implements FactoryBean<Sourc
|
||||
this.autoStartup = autoStartup;
|
||||
}
|
||||
|
||||
public void setPhase(int phase) {
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory,
|
||||
"a ConfigurableBeanFactory is required");
|
||||
@@ -145,6 +151,7 @@ public class SourcePollingChannelAdapterFactoryBean implements FactoryBean<Sourc
|
||||
spca.setErrorHandler(this.pollerMetadata.getErrorHandler());
|
||||
spca.setBeanClassLoader(this.beanClassLoader);
|
||||
spca.setAutoStartup(this.autoStartup);
|
||||
spca.setPhase(this.phase);
|
||||
spca.setBeanName(this.beanName);
|
||||
spca.setBeanFactory(this.beanFactory);
|
||||
spca.setTransactionSynchronizationFactory(this.pollerMetadata.getTransactionSynchronizationFactory());
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.integration.config.xml;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
@@ -27,7 +29,6 @@ import org.springframework.integration.config.ConsumerEndpointFactoryBean;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* Base class for outbound Channel Adapter parsers.
|
||||
@@ -71,6 +72,7 @@ public abstract class AbstractOutboundChannelAdapterParser extends AbstractChann
|
||||
}
|
||||
builder.addPropertyValue("inputChannelName", channelName);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "phase");
|
||||
|
||||
this.configureRequestHandlerAdviceChain(element, parserContext, handlerBeanComponentDefinition.getBeanDefinition(), builder);
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -50,6 +50,7 @@ public abstract class AbstractPollingInboundChannelAdapterParser extends Abstrac
|
||||
IntegrationNamespaceUtils.configurePollerMetadata(pollerElement, adapterBuilder, parserContext);
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "auto-startup");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "phase");
|
||||
return adapterBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -18,20 +18,20 @@ package org.springframework.integration.config.xml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.endpoint.ExpressionEvaluatingMessageSource;
|
||||
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.endpoint.ExpressionEvaluatingMessageSource;
|
||||
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
|
||||
import org.springframework.integration.expression.DynamicExpression;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
@@ -46,23 +46,34 @@ public class DefaultInboundChannelAdapterParser extends AbstractPollingInboundCh
|
||||
|
||||
@Override
|
||||
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
|
||||
Object source = parserContext.extractSource(element);
|
||||
BeanMetadataElement result = null;
|
||||
BeanComponentDefinition innnerBeanDef = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
|
||||
String sourceRef = element.getAttribute(IntegrationNamespaceUtils.REF_ATTRIBUTE);
|
||||
String methodName = element.getAttribute(IntegrationNamespaceUtils.METHOD_ATTRIBUTE);
|
||||
String expressionString = element.getAttribute(IntegrationNamespaceUtils.EXPRESSION_ATTRIBUTE);
|
||||
Element scriptElement = DomUtils.getChildElementByTagName(element, "script");
|
||||
Element expressionElement = DomUtils.getChildElementByTagName(element, "expression");
|
||||
|
||||
boolean isInnerDef = innnerBeanDef != null;
|
||||
boolean isRef = StringUtils.hasText(sourceRef);
|
||||
boolean isExpression = StringUtils.hasText(expressionString);
|
||||
boolean hasInnerDef = innnerBeanDef != null;
|
||||
boolean hasRef = StringUtils.hasText(sourceRef);
|
||||
boolean hasExpression = StringUtils.hasText(expressionString);
|
||||
boolean hasScriptElement = scriptElement != null;
|
||||
boolean hasExpressionElement = expressionElement != null;
|
||||
boolean hasMethod = StringUtils.hasText(methodName);
|
||||
|
||||
if (!(isInnerDef ^ (isRef ^ isExpression))) {
|
||||
if (!hasInnerDef && !hasRef && !hasExpression && !hasScriptElement && !hasExpressionElement) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Exactly one of the 'ref', 'expression' or inner bean is required.", element);
|
||||
"Exactly one of the 'ref', 'expression', inner bean, <script> or <expression> is required.", element);
|
||||
}
|
||||
|
||||
if (isInnerDef) {
|
||||
if (hasInnerDef) {
|
||||
if (hasRef || hasExpression) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Neither 'ref' nor 'expression' are permitted when an inner bean (<bean/>) is configured on element " +
|
||||
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
|
||||
return null;
|
||||
}
|
||||
if (hasMethod) {
|
||||
result = this.parseMethodInvokingSource(innnerBeanDef, methodName, element, parserContext);
|
||||
}
|
||||
@@ -70,15 +81,34 @@ public class DefaultInboundChannelAdapterParser extends AbstractPollingInboundCh
|
||||
result = innnerBeanDef;
|
||||
}
|
||||
}
|
||||
else if (isExpression) {
|
||||
if (hasMethod) {
|
||||
else if (hasScriptElement) {
|
||||
if (hasRef || hasMethod || hasExpression) {
|
||||
parserContext.getReaderContext().error(
|
||||
"The 'method' attribute can't be used with 'expression' attribute.", element);
|
||||
"Neither 'ref' and 'method' nor 'expression' are permitted when an inner script element is configured on element " +
|
||||
IntegrationNamespaceUtils.createElementDescription(element) + ".", source);
|
||||
return null;
|
||||
}
|
||||
String expressionBeanName = this.parseExpression(expressionString, element, parserContext);
|
||||
result = new RuntimeBeanReference(expressionBeanName);
|
||||
BeanDefinition scriptBeanDefinition = parserContext.getDelegate().parseCustomElement(scriptElement);
|
||||
BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
IntegrationNamespaceUtils.BASE_PACKAGE + ".scripting.ScriptExecutingMessageSource");
|
||||
sourceBuilder.addConstructorArgValue(scriptBeanDefinition);
|
||||
this.parseHeaderExpressions(sourceBuilder, element, parserContext);
|
||||
result = sourceBuilder.getBeanDefinition();
|
||||
}
|
||||
else if (isRef) {
|
||||
else if (hasExpression || hasExpressionElement) {
|
||||
if (hasRef || hasMethod) {
|
||||
parserContext.getReaderContext().error(
|
||||
"The 'ref' and 'method' attributes can't be used with 'expression' attribute or inner <expression>.", element);
|
||||
return null;
|
||||
}
|
||||
if (hasExpression & hasExpressionElement) {
|
||||
parserContext.getReaderContext().error(
|
||||
"Exactly one of the 'expression' attribute or inner <expression> is required.", element);
|
||||
return null;
|
||||
}
|
||||
result = this.parseExpression(expressionString, expressionElement, element, parserContext);
|
||||
}
|
||||
else if (hasRef) {
|
||||
BeanMetadataElement sourceValue = new RuntimeBeanReference(sourceRef);
|
||||
if (hasMethod) {
|
||||
result = this.parseMethodInvokingSource(sourceValue, methodName, element, parserContext);
|
||||
@@ -87,31 +117,44 @@ public class DefaultInboundChannelAdapterParser extends AbstractPollingInboundCh
|
||||
result = sourceValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
parserContext.getReaderContext().error("One of the following is required: " +
|
||||
"'ref' attribute, 'expression' attribute, or an inner-bean definition.", element);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private BeanMetadataElement parseMethodInvokingSource(BeanMetadataElement targetObject, String methodName, Element element, ParserContext parserContext) {
|
||||
private BeanMetadataElement parseMethodInvokingSource(BeanMetadataElement targetObject, String methodName, Element element,
|
||||
ParserContext parserContext) {
|
||||
BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingMessageSource.class);
|
||||
sourceBuilder.addPropertyValue("object", targetObject);
|
||||
sourceBuilder.addPropertyValue("methodName", methodName);
|
||||
this.parseHeaderExpressions(sourceBuilder, element, parserContext);
|
||||
String sourceRef = BeanDefinitionReaderUtils.registerWithGeneratedName(
|
||||
sourceBuilder.getBeanDefinition(), parserContext.getRegistry());
|
||||
return new RuntimeBeanReference(sourceRef);
|
||||
return sourceBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
private String parseExpression(String expressionString, Element element, ParserContext parserContext) {
|
||||
private BeanMetadataElement parseExpression(String expressionString, Element expressionElement, Element element,
|
||||
ParserContext parserContext) {
|
||||
BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionEvaluatingMessageSource.class);
|
||||
RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
|
||||
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expressionString);
|
||||
|
||||
BeanDefinition expressionDef = null;
|
||||
|
||||
if (StringUtils.hasText(expressionString)) {
|
||||
expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
|
||||
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expressionString);
|
||||
}
|
||||
else {
|
||||
BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
DynamicExpression.class);
|
||||
String key = expressionElement.getAttribute("key");
|
||||
String expressionSourceReference = expressionElement.getAttribute("source");
|
||||
dynamicExpressionBuilder.addConstructorArgValue(key);
|
||||
dynamicExpressionBuilder.addConstructorArgReference(expressionSourceReference);
|
||||
expressionDef = dynamicExpressionBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
sourceBuilder.addConstructorArgValue(expressionDef);
|
||||
sourceBuilder.addConstructorArgValue(null);
|
||||
|
||||
this.parseHeaderExpressions(sourceBuilder, element, parserContext);
|
||||
return BeanDefinitionReaderUtils.registerWithGeneratedName(sourceBuilder.getBeanDefinition(), parserContext.getRegistry());
|
||||
return sourceBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
private void parseHeaderExpressions(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
|
||||
@@ -120,24 +163,8 @@ public class DefaultInboundChannelAdapterParser extends AbstractPollingInboundCh
|
||||
ManagedMap<String, Object> headerExpressions = new ManagedMap<String, Object>();
|
||||
for (Element headerElement : headerElements) {
|
||||
String headerName = headerElement.getAttribute("name");
|
||||
String headerValue = headerElement.getAttribute("value");
|
||||
String headerExpression = headerElement.getAttribute("expression");
|
||||
boolean hasValue = StringUtils.hasText(headerValue);
|
||||
boolean hasExpression = StringUtils.hasText(headerExpression);
|
||||
if (!(hasValue ^ hasExpression)) {
|
||||
parserContext.getReaderContext().error("exactly one of 'value' or 'expression' is required on a header sub-element",
|
||||
parserContext.extractSource(headerElement));
|
||||
continue;
|
||||
}
|
||||
RootBeanDefinition expressionDef = null;
|
||||
if (hasValue) {
|
||||
expressionDef = new RootBeanDefinition(LiteralExpression.class);
|
||||
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(headerValue);
|
||||
}
|
||||
else {
|
||||
expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
|
||||
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(headerExpression);
|
||||
}
|
||||
BeanDefinition expressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("value",
|
||||
"expression", parserContext, headerElement, true);
|
||||
headerExpressions.put(headerName, expressionDef);
|
||||
}
|
||||
builder.addPropertyValue("headerExpressions", headerExpressions);
|
||||
|
||||
@@ -800,6 +800,9 @@
|
||||
<xsd:documentation>
|
||||
Defines a Channel Adapter that receives from a MessageSource and sends to a
|
||||
MessageChannel.
|
||||
Note, when using the 'expression' attribute, or 'expression' or 'script' sub-element,
|
||||
there is not yet a root Message object and therefore
|
||||
the 'payload' and 'headers' properties are not available in the expression or script.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
@@ -807,10 +810,16 @@
|
||||
<xsd:choice minOccurs="0" maxOccurs="1" >
|
||||
<xsd:sequence>
|
||||
<xsd:element name="poller" type="basePollerType" />
|
||||
<xsd:element ref="beans:bean" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:choice minOccurs="0" maxOccurs="1" >
|
||||
<xsd:element name="expression" type="innerExpressionType"/>
|
||||
<xsd:any namespace="##other"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="beans:bean" />
|
||||
<xsd:choice minOccurs="0" maxOccurs="1" >
|
||||
<xsd:element name="expression" type="innerExpressionType"/>
|
||||
<xsd:any namespace="##other"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="poller" type="basePollerType" minOccurs="0" maxOccurs="1" />
|
||||
</xsd:sequence>
|
||||
</xsd:choice>
|
||||
@@ -1039,6 +1048,16 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="phase" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
The Lifecycle attribute determining the start/stop order
|
||||
of the underlying MessageHandlerChain.
|
||||
</xsd:documentation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:complexType name="methodInvokingChannelAdapterType">
|
||||
|
||||
@@ -15,21 +15,22 @@
|
||||
<queue capacity="1"/>
|
||||
</channel>
|
||||
|
||||
<outbound-channel-adapter id="outboundWithImplicitChannel" ref="consumer"/>
|
||||
<outbound-channel-adapter id="outboundWithImplicitChannel" ref="consumer" auto-startup="false" phase="-1"/>
|
||||
|
||||
<outbound-channel-adapter id="methodInvokingConsumer" ref="testBean" method="store"/>
|
||||
|
||||
<outbound-channel-adapter id="expressionConsumer" expression="@testBean.store(payload)"/>
|
||||
|
||||
<inbound-channel-adapter id="methodInvokingSource" ref="testBean" method="getMessage"
|
||||
channel="queueChannel" auto-startup="false">
|
||||
<inbound-channel-adapter id="methodInvokingSource" ref="testBean" method="getMessage"
|
||||
channel="queueChannel" auto-startup="false" phase="-1">
|
||||
<poller max-messages-per-poll="1" fixed-delay="10000"/>
|
||||
</inbound-channel-adapter>
|
||||
|
||||
|
||||
<channel id="withTimeoutChannel">
|
||||
<queue/>
|
||||
</channel>
|
||||
<inbound-channel-adapter id="methodInvokingSourceWithTimeout" ref="testBean" method="getMessage"
|
||||
|
||||
<inbound-channel-adapter id="methodInvokingSourceWithTimeout" ref="testBean" method="getMessage"
|
||||
channel="withTimeoutChannel" auto-startup="false"
|
||||
send-timeout="999">
|
||||
<poller max-messages-per-poll="1" fixed-rate="800"/>
|
||||
|
||||
@@ -17,10 +17,14 @@
|
||||
package org.springframework.integration.config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
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 org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -30,6 +34,8 @@ import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessageDeliveryException;
|
||||
import org.springframework.integration.MessageDispatchingException;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
@@ -73,6 +79,7 @@ public class ChannelAdapterParserTests {
|
||||
Object adapter = this.applicationContext.getBean(beanName);
|
||||
assertNotNull(adapter);
|
||||
assertTrue(adapter instanceof SourcePollingChannelAdapter);
|
||||
assertEquals(-1, ((SourcePollingChannelAdapter) adapter).getPhase());
|
||||
this.applicationContext.start();
|
||||
Message<?> message = channel.receive(1000);
|
||||
assertNotNull(message);
|
||||
@@ -110,10 +117,22 @@ public class ChannelAdapterParserTests {
|
||||
Object adapter = this.applicationContext.getBean(beanName + ".adapter");
|
||||
assertNotNull(adapter);
|
||||
assertTrue(adapter instanceof EventDrivenConsumer);
|
||||
assertFalse(((EventDrivenConsumer) adapter).isAutoStartup());
|
||||
assertEquals(-1, ((EventDrivenConsumer) adapter).getPhase());
|
||||
TestConsumer consumer = (TestConsumer) this.applicationContext.getBean("consumer");
|
||||
assertNull(consumer.getLastMessage());
|
||||
Message<?> message = new GenericMessage<String>("test");
|
||||
assertTrue(((MessageChannel) channel).send(message));
|
||||
try {
|
||||
((MessageChannel) channel).send(message);
|
||||
fail("MessageDispatchingException is expected.");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e, Matchers.instanceOf(MessageDeliveryException.class));
|
||||
assertThat(e.getCause(), Matchers.instanceOf(MessageDispatchingException.class));
|
||||
}
|
||||
|
||||
((EventDrivenConsumer) adapter).start();
|
||||
((MessageChannel) channel).send(message);
|
||||
assertNotNull(consumer.getLastMessage());
|
||||
assertEquals(message, consumer.getLastMessage());
|
||||
}
|
||||
|
||||
@@ -41,6 +41,15 @@
|
||||
<poller trigger="customTrigger"/>
|
||||
</inbound-channel-adapter>
|
||||
|
||||
<inbound-channel-adapter id="expressionElement" channel="triggerRefChannel">
|
||||
<poller fixed-delay="1000"/>
|
||||
<expression key="test.greeting"/>
|
||||
</inbound-channel-adapter>
|
||||
|
||||
<beans:bean id="expressionSource" class="org.springframework.integration.expression.ReloadableResourceBundleExpressionSource">
|
||||
<beans:property name="basename" value="org/springframework/integration/expression/expressions"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="customTrigger" class="org.springframework.scheduling.support.PeriodicTrigger">
|
||||
<beans:constructor-arg value="9999"/>
|
||||
</beans:bean>
|
||||
|
||||
@@ -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.
|
||||
@@ -38,6 +38,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
* @since 2.0
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@@ -109,7 +110,7 @@ public class InboundChannelAdapterExpressionTests {
|
||||
public void headerExpressions() {
|
||||
SourcePollingChannelAdapter adapter = context.getBean("headerExpressionsProducer", SourcePollingChannelAdapter.class);
|
||||
assertFalse(adapter.isAutoStartup());
|
||||
Map<String, Expression> headerExpressions = TestUtils.getPropertyValue(adapter, "source.headerExpressions", Map.class);
|
||||
Map<String, Expression> headerExpressions = TestUtils.getPropertyValue(adapter, "source.headerExpressions", Map.class);
|
||||
assertEquals(2, headerExpressions.size());
|
||||
assertEquals("6 * 7", headerExpressions.get("foo").getExpressionString());
|
||||
assertEquals("x", headerExpressions.get("bar").getExpressionString());
|
||||
@@ -117,4 +118,12 @@ public class InboundChannelAdapterExpressionTests {
|
||||
assertEquals("x", headerExpressions.get("bar").getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt2867InnerExpression() {
|
||||
SourcePollingChannelAdapter adapter = context.getBean("expressionElement", SourcePollingChannelAdapter.class);
|
||||
Expression expression = TestUtils.getPropertyValue(adapter, "source.expression", Expression.class);
|
||||
assertEquals("'Hello World!'", expression.getExpressionString());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.scripting;
|
||||
|
||||
import org.springframework.integration.endpoint.AbstractMessageSource;
|
||||
|
||||
/**
|
||||
* The {@link org.springframework.integration.core.MessageSource} strategy implementation
|
||||
* to produce a {@link org.springframework.integration.Message} from underlying
|
||||
* {@linkplain #scriptMessageProcessor} for polling endpoints.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
class ScriptExecutingMessageSource extends AbstractMessageSource<Object> {
|
||||
|
||||
private final AbstractScriptExecutingMessageProcessor<?> scriptMessageProcessor;
|
||||
|
||||
public ScriptExecutingMessageSource(AbstractScriptExecutingMessageProcessor<?> scriptMessageProcessor) {
|
||||
this.scriptMessageProcessor = scriptMessageProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doReceive() {
|
||||
return scriptMessageProcessor.processMessage(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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:script="http://www.springframework.org/schema/integration/scripting"
|
||||
xsi:schemaLocation="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
|
||||
http://www.springframework.org/schema/integration/scripting http://www.springframework.org/schema/integration/scripting/spring-integration-scripting.xsd">
|
||||
|
||||
<channel id="inbound-channel-adapter-channel">
|
||||
<queue/>
|
||||
</channel>
|
||||
|
||||
<inbound-channel-adapter channel="inbound-channel-adapter-channel">
|
||||
<poller max-messages-per-poll="1" fixed-delay="100"/>
|
||||
<script:script lang="ruby">
|
||||
<![CDATA[
|
||||
require "java"
|
||||
include_class 'java.util.Date'
|
||||
|
||||
Date.new
|
||||
]]>
|
||||
</script:script>
|
||||
<header name="foo" value="bar"/>
|
||||
</inbound-channel-adapter>
|
||||
|
||||
</beans:beans>
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.scripting.config.jsr223;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.core.PollableChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @since 2.0
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class Jsr223InboundChannelAdapterTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("inbound-channel-adapter-channel")
|
||||
private PollableChannel inboundChannelAdapterChannel;
|
||||
|
||||
@Test
|
||||
public void testInt2867InboundChannelAdapter() throws Exception {
|
||||
Message<?> message = this.inboundChannelAdapterChannel.receive(2000);
|
||||
assertNotNull(message);
|
||||
Object payload = message.getPayload();
|
||||
assertThat(payload, Matchers.instanceOf(Date.class));
|
||||
assertTrue(((Date) payload).before(new Date()));
|
||||
assertEquals("bar", message.getHeaders().get("foo"));
|
||||
|
||||
message = this.inboundChannelAdapterChannel.receive(1000);
|
||||
assertNotNull(message);
|
||||
|
||||
message = this.inboundChannelAdapterChannel.receive(10);
|
||||
assertNull(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,9 @@
|
||||
<int:poller cron="30 * 9-17 * * MON-FRI"/>
|
||||
</int:channel-adapter>]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Also see <xref linkend="channel-adapter-expressions-and-scripts"/>.
|
||||
</para>
|
||||
<note>
|
||||
<para>
|
||||
If no poller is provided, then a single default poller must be registered within the context.
|
||||
@@ -121,6 +124,9 @@
|
||||
of the <code><inbound-channel-adapter></code> or <code><outbound-channel-adapter></code> element. Therefore, if the "channel"
|
||||
is not provided, the "id" is required.
|
||||
</para>
|
||||
</section>
|
||||
<section id="channel-adapter-expressions-and-scripts">
|
||||
<title>Channel Adapter Expressions and Scripts</title>
|
||||
<para>
|
||||
Like many other Spring Integration components, the <code><inbound-channel-adapter></code> and
|
||||
<code><outbound-channel-adapter></code> also provide support for SpEL expression evaluation. To use SpEL, provide the
|
||||
@@ -130,6 +136,37 @@
|
||||
evaluation result is a <emphasis>non-null</emphasis> value, while the <emphasis>expression</emphasis> for an
|
||||
<code><outbound-channel-adapter></code> must be the equivalent of a <emphasis>void</emphasis> returning method invocation.
|
||||
</para>
|
||||
<para>
|
||||
Starting with Spring Integration 3.0, an <code><int:inbound-channel-adapter/></code> can also be configured
|
||||
with a SpEL <code><expression/></code> (or even with <code><script/></code>) sub-element, for when
|
||||
more sophistication is required than can be achieved with the simple 'expression' attribute.
|
||||
If you provide a script as a <interfacename>Resource</interfacename> using the <code>location</code>
|
||||
attribute, you can also set the <emphasis>refresh-check-delay</emphasis> allowing the resource to be refreshed
|
||||
periodically.
|
||||
If you want the script to be checked on each poll, you would need to coordinate this setting with the poller's trigger:
|
||||
</para>
|
||||
<programlisting language="xml"><![CDATA[<int:inbound-channel-adapter ref="source1" method="method1" channel="channel1">
|
||||
<int:poller max-messages-per-poll="1" fixed-delay="5000"/>
|
||||
<script:script lang="ruby" location="Foo.rb" refresh-check-delay="5000"/>
|
||||
</int:inbound-channel-adapter>]]></programlisting>
|
||||
<para>
|
||||
Also see the <code>cacheSeconds</code> property on the <classname>ReloadableResourceBundleExpressionSource</classname>
|
||||
when using the <code><expression/></code> sub-element.
|
||||
For more information regarding expressions see <xref linkend="spel"/>, and for scripts - <xref linkend="groovy"/>
|
||||
and <xref linkend="scripting"/>.
|
||||
</para>
|
||||
<important>
|
||||
<para>
|
||||
The <code><int:inbound-channel-adapter/></code> is an endpoint that starts a message flow
|
||||
via periodic triggering to poll some underlying <interfacename>MessageSource</interfacename>. Since, at the
|
||||
time of polling, there is not yet a message object, expressions and scripts don't
|
||||
have access to a root <classname>Message</classname>, so there are no <emphasis>payload</emphasis>
|
||||
or <emphasis>headers</emphasis> properties that are available in most other messaging SpEL expressions.
|
||||
Of course, the script <emphasis role="bold">can</emphasis>
|
||||
generate and return a complete <classname>Message</classname> object with headers and payload, or just
|
||||
a payload, which will be added to a message with basic headers.
|
||||
</para>
|
||||
</important>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -75,8 +75,15 @@
|
||||
<title>'Tail' Support</title>
|
||||
<para>
|
||||
File 'tail'ing inbound channel adapters are now provided to generate messages when
|
||||
lines are added to the end of text files.
|
||||
<xref linkend="file-tailing"/>.
|
||||
lines are added to the end of text files; see <xref linkend="file-tailing"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-inbound-script">
|
||||
<title>Inbound Channel Adapter Script Support</title>
|
||||
<para>
|
||||
The <code><int:inbound-channel-adapter/></code> now supports <code><expression/></code>
|
||||
and <code><script/></code> sub-elements to create a
|
||||
<interfacename>MessageSource</interfacename>; see <xref linkend="channel-adapter-expressions-and-scripts"/>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="3.0-spel-customization">
|
||||
|
||||
Reference in New Issue
Block a user