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:
Artem Bilan
2013-09-17 21:59:41 +03:00
committed by Gary Russell
parent 63cbf736a2
commit 09be87551b
15 changed files with 344 additions and 64 deletions

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.
@@ -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;
}

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.
@@ -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());

View File

@@ -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);

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.
@@ -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();
}

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.
@@ -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);

View File

@@ -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">

View File

@@ -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"/>

View File

@@ -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());
}

View File

@@ -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>

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.
@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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>&lt;inbound-channel-adapter&gt;</code> or <code>&lt;outbound-channel-adapter&gt;</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>&lt;inbound-channel-adapter&gt;</code> and
<code>&lt;outbound-channel-adapter&gt;</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>&lt;outbound-channel-adapter&gt;</code> must be the equivalent of a <emphasis>void</emphasis> returning method invocation.
</para>
<para>
Starting with Spring Integration 3.0, an <code>&lt;int:inbound-channel-adapter/&gt;</code> can also be configured
with a SpEL <code>&lt;expression/&gt;</code> (or even with <code>&lt;script/&gt;</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>&lt;expression/&gt;</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>&lt;int:inbound-channel-adapter/&gt;</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>

View File

@@ -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>&lt;int:inbound-channel-adapter/&gt;</code> now supports <code>&lt;expression/&gt;</code>
and <code>&lt;script/&gt;</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">