From 869a8de05f20c0d0dcc0591ce923f269778179e9 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 18 Sep 2014 20:17:51 +0300 Subject: [PATCH] INT-275: Implement Scatter-Gather Pattern JIRA: https://jira.spring.io/browse/INT-275 In addition fix the `Lifecycle` issue in the `ServiceActivatorAnnotationPostProcessor` Add Namespace support, Addition tests and fix some typos in the XSD INT-275: Fix failed tests INT-275: Fix for `replyChannel` Header Add `sync` reply test-case INT-275: Make `ScatterGatherHandler` sync Fix some `MessageHandler`s from `SmartLifecycle` INT-275: Fix `ScatterGatherHandler.handleRequestMessage` logic INT-275: Fix `ScatterGatherHandler` JMX proxying issues * Make `AbstractCorrelatingMessageHandler.getMessageStore()` as `public` * Move `ScatterGatherHandlerIntegrationTests` to the JMX module to be sure that `ScatterGatherHandler` works well with `@EnableIntegrationMBeanExport` * Add xml config sample how to use an internal gatherer's `MessageStore` in the `MessageGroupStoreReaper` * Add `What's New` notice --- .../AbstractCorrelatingMessageHandler.java | 2 +- ...rviceActivatorAnnotationPostProcessor.java | 48 ++- .../xml/IntegrationNamespaceHandler.java | 1 + .../config/xml/ScatterGatherParser.java | 118 ++++++ .../handler/ScatterGatherHandler.java | 180 +++++++++ .../config/xml/spring-integration-4.1.xsd | 100 ++++- .../ScatterGatherParserTests-context.xml | 39 ++ .../config/ScatterGatherParserTests.java | 101 +++++ .../config/ScatterGatherTests-context.xml | 61 +++ .../config/ScatterGatherTests.java | 86 +++++ .../ip/config/TcpOutboundGatewayParser.java | 12 +- .../ip/tcp/TcpOutboundGateway.java | 31 +- .../ip/config/ParserUnitTests.java | 2 - .../integration/jms/JmsOutboundGateway.java | 26 +- .../ScatterGatherHandlerIntegrationTests.java | 361 ++++++++++++++++++ .../mqtt/config/xml/MqttParserUtils.java | 2 - .../outbound/AbstractMqttMessageHandler.java | 34 +- ...MqttOutboundChannelAdapterParserTests.java | 4 - src/reference/docbook/message-routing.xml | 1 + src/reference/docbook/scatter-gather.xml | 26 ++ src/reference/docbook/whats-new.xml | 7 + 21 files changed, 1120 insertions(+), 122 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/xml/ScatterGatherParser.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/handler/ScatterGatherHandler.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherParserTests-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherParserTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherTests-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherTests.java create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ScatterGatherHandlerIntegrationTests.java create mode 100644 src/reference/docbook/scatter-gather.xml diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java index c9c8c4fa57..cb59ad23f8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java @@ -304,7 +304,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP return "aggregator"; } - protected MessageGroupStore getMessageStore() { + public MessageGroupStore getMessageStore() { return messageStore; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java index 2c4fd7f799..4abaf436d7 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/ServiceActivatorAnnotationPostProcessor.java @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import java.util.List; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.context.Lifecycle; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.env.Environment; @@ -58,15 +59,7 @@ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnot * Return a reply-producing message handler so that we still get 'produced no reply' messages * and the super class will inject the advice chain to advise the handler method if needed. */ - return new AbstractReplyProducingMessageHandler() { - - @Override - protected Object handleRequestMessage(Message requestMessage) { - - ((MessageHandler) target).handleMessage(requestMessage); - return null; - } - }; + return new ReplyProducingMessageHandlerWrapper((MessageHandler) target); } else { serviceActivator = new ServiceActivatingHandler(target); @@ -89,4 +82,41 @@ public class ServiceActivatorAnnotationPostProcessor extends AbstractMethodAnnot return serviceActivator; } + private class ReplyProducingMessageHandlerWrapper extends AbstractReplyProducingMessageHandler + implements Lifecycle { + + private final MessageHandler target; + + private ReplyProducingMessageHandlerWrapper(MessageHandler target) { + this.target = target; + } + + @Override + protected Object handleRequestMessage(Message requestMessage) { + this.target.handleMessage(requestMessage); + return null; + } + + @Override + public void start() { + if (this.target instanceof Lifecycle) { + ((Lifecycle) this.target).start(); + } + + } + + @Override + public void stop() { + if (this.target instanceof Lifecycle) { + ((Lifecycle) this.target).stop(); + } + } + + @Override + public boolean isRunning() { + return !(this.target instanceof Lifecycle) || ((Lifecycle) this.target).isRunning(); + } + + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java index b8a13d9b5e..59f9b4d1fd 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java @@ -81,6 +81,7 @@ public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHan RetryAdviceParser retryParser = new RetryAdviceParser(); registerBeanDefinitionParser("handler-retry-advice", retryParser); registerBeanDefinitionParser("retry-advice", retryParser); + registerBeanDefinitionParser("scatter-gather", new ScatterGatherParser()); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ScatterGatherParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ScatterGatherParser.java new file mode 100644 index 0000000000..a1e05f201e --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ScatterGatherParser.java @@ -0,0 +1,118 @@ +/* + * Copyright 2014 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.xml; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.handler.ScatterGatherHandler; +import org.springframework.integration.router.RecipientListRouter; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; + +/** + * Parser for the <scatter-gather> element. + * + * @author Artem Bilan + * @since 4.1 + */ +public class ScatterGatherParser extends AbstractConsumerEndpointParser { + + private static final RecipientListRouterParser SCATTERER_PARSER = new RecipientListRouterParser(); + + private static final AggregatorParser GATHERER_PARSER = new AggregatorParser(); + + private static final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + + @Override + protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { + String scatterChannel = element.getAttribute("scatter-channel"); + boolean hasScatterChannel = StringUtils.hasText(scatterChannel); + Element scatterer = DomUtils.getChildElementByTagName(element, "scatterer"); + boolean hasScatterer = scatterer != null; + + if (hasScatterChannel & hasScatterer) { + parserContext.getReaderContext() + .error("'scatter-channel' attribute and 'scatterer' sub-element are mutually exclusive", element); + } + + if (!hasScatterChannel & !hasScatterer) { + parserContext.getReaderContext() + .error("The 'scatter-channel' attribute or 'scatterer' sub-element must be specified", element); + } + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ScatterGatherHandler.class); + AbstractBeanDefinition scatterGatherDefinition = builder.getRawBeanDefinition(); + String id = resolveId(element, scatterGatherDefinition, parserContext); + + if (hasScatterChannel) { + builder.addConstructorArgReference(scatterChannel); + } + else { + BeanDefinition scattererDefinition = null; + if (!hasScatterer) { + scattererDefinition = new RootBeanDefinition(RecipientListRouter.class); + } + else { + scattererDefinition = SCATTERER_PARSER.parse(scatterer, + new ParserContext(parserContext.getReaderContext(), parserContext.getDelegate(), + scatterGatherDefinition)); + } + + String scattererId = id + ".scatterer"; + if (hasScatterer && scatterer.hasAttribute(ID_ATTRIBUTE)) { + scattererId = scatterer.getAttribute(ID_ATTRIBUTE); + } + parserContext.getRegistry().registerBeanDefinition(scattererId, scattererDefinition); + builder.addConstructorArgValue(new RuntimeBeanReference(scattererId)); + } + + Element gatherer = DomUtils.getChildElementByTagName(element, "gatherer"); + + BeanDefinition gathererDefinition = null; + if (gatherer == null) { + try { + gatherer = documentBuilderFactory.newDocumentBuilder().newDocument().createElement("aggregator"); + } + catch (ParserConfigurationException e) { + parserContext.getReaderContext().error(e.getMessage(), element); + } + } + gathererDefinition = GATHERER_PARSER.parse(gatherer, new ParserContext(parserContext.getReaderContext(), + parserContext.getDelegate(), scatterGatherDefinition)); + String gathererId = id + ".gatherer"; + if (gatherer != null && gatherer.hasAttribute(ID_ATTRIBUTE)) { + gathererId = gatherer.getAttribute(ID_ATTRIBUTE); + } + parserContext.getRegistry().registerBeanDefinition(gathererId, gathererDefinition); + builder.addConstructorArgValue(new RuntimeBeanReference(gathererId)); + + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "gather-channel"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "gather-timeout"); + + return builder; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/ScatterGatherHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/ScatterGatherHandler.java new file mode 100644 index 0000000000..a863b93923 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/ScatterGatherHandler.java @@ -0,0 +1,180 @@ +/* + * Copyright 2014 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.handler; + +import org.springframework.aop.support.AopUtils; +import org.springframework.context.Lifecycle; +import org.springframework.integration.aggregator.AggregatingMessageHandler; +import org.springframework.integration.channel.FixedSubscriberChannel; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.context.IntegrationContextUtils; +import org.springframework.integration.core.MessageProducer; +import org.springframework.integration.endpoint.AbstractEndpoint; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.endpoint.PollingConsumer; +import org.springframework.integration.router.RecipientListRouter; +import org.springframework.integration.support.channel.HeaderChannelRegistry; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageDeliveryException; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.util.Assert; + +/** + * The {@link MessageHandler} implementation for the + * Scatter-Gather EIP pattern. + * + * @author Artem Bilan + * @since 4.1 + */ +public class ScatterGatherHandler extends AbstractReplyProducingMessageHandler implements Lifecycle { + + private static final String GATHER_RESULT_CHANNEL = "gatherResultChannel"; + + private final MessageChannel scatterChannel; + + private final MessageHandler gatherer; + + private MessageChannel gatherChannel; + + private long gatherTimeout = -1; + + private AbstractEndpoint gatherEndpoint; + + private HeaderChannelRegistry replyChannelRegistry; + + + public ScatterGatherHandler(MessageChannel scatterChannel, MessageHandler gatherer) { + Assert.notNull(scatterChannel); + Assert.notNull(gatherer); + Class gathererClass = AopUtils.getTargetClass(gatherer); + Assert.isAssignable(AggregatingMessageHandler.class, gathererClass, + "the 'gatherer' must be an AggregatingMessageHandler instance"); + this.scatterChannel = scatterChannel; + this.gatherer = gatherer; + } + + public ScatterGatherHandler(MessageHandler scatterer, MessageHandler gatherer) { + this(new FixedSubscriberChannel(scatterer), gatherer); + Assert.notNull(scatterer); + Class scatterClass = AopUtils.getTargetClass(scatterer); + Assert.isAssignable(RecipientListRouter.class, scatterClass, + "the 'scatterer' must be a RecipientListRouter instance"); + } + + public void setGatherChannel(MessageChannel gatherChannel) { + this.gatherChannel = gatherChannel; + } + + public void setGatherTimeout(long gatherTimeout) { + this.gatherTimeout = gatherTimeout; + } + + @Override + protected void doInit() { + if (this.gatherChannel == null) { + this.gatherChannel = new FixedSubscriberChannel(this.gatherer); + } + else { + if (this.gatherChannel instanceof SubscribableChannel) { + this.gatherEndpoint = new EventDrivenConsumer((SubscribableChannel) this.gatherChannel, this.gatherer); + } + else if (this.gatherChannel instanceof PollableChannel) { + this.gatherEndpoint = new PollingConsumer((PollableChannel) this.gatherChannel, this.gatherer); + ((PollingConsumer) this.gatherEndpoint).setReceiveTimeout(this.gatherTimeout); + } + else { + throw new MessagingException("Unsupported 'replyChannel' type [" + this.gatherChannel.getClass() + "]." + + "SubscribableChannel or PollableChannel type are supported."); + } + this.gatherEndpoint.setBeanFactory(this.getBeanFactory()); + this.gatherEndpoint.afterPropertiesSet(); + } + + ((MessageProducer) this.gatherer).setOutputChannel(new FixedSubscriberChannel(new MessageHandler() { + + @Override + public void handleMessage(Message message) throws MessagingException { + MessageHeaders headers = message.getHeaders(); + if (headers.containsKey(GATHER_RESULT_CHANNEL)) { + Object gatherResultChannel = headers.get(GATHER_RESULT_CHANNEL); + if (gatherResultChannel instanceof MessageChannel) { + messagingTemplate.send((MessageChannel) gatherResultChannel, message); + } + else if (gatherResultChannel instanceof String) { + messagingTemplate.send((String) gatherResultChannel, message); + } + } + else { + throw new MessageDeliveryException(message, + "The 'gatherResultChannel' header is required to delivery gather result."); + } + } + + })); + + this.replyChannelRegistry = getBeanFactory() + .getBean(IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME, + HeaderChannelRegistry.class); + } + + @Override + protected Object handleRequestMessage(Message requestMessage) { + PollableChannel gatherResultChannel = new QueueChannel(); + + Object gatherResultChannelName = this.replyChannelRegistry.channelToChannelName(gatherResultChannel); + + Message scatterMessage = getMessageBuilderFactory() + .fromMessage(requestMessage) + .setHeader(GATHER_RESULT_CHANNEL, gatherResultChannelName) + .setReplyChannel(this.gatherChannel) + .build(); + + this.messagingTemplate.send(this.scatterChannel, scatterMessage); + + Message gatherResult = gatherResultChannel.receive(this.gatherTimeout); + if (gatherResult != null) { + return gatherResult.getPayload(); + } + + return null; + } + + @Override + public void start() { + if (this.gatherEndpoint != null) { + gatherEndpoint.start(); + } + } + + @Override + public void stop() { + if (this.gatherEndpoint != null) { + gatherEndpoint.start(); + } + } + + @Override + public boolean isRunning() { + return this.gatherEndpoint == null || this.gatherEndpoint.isRunning(); + } + +} diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.1.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.1.xsd index f21f7aa3b1..dd888a5cac 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.1.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-4.1.xsd @@ -680,8 +680,8 @@ @@ -762,7 +762,7 @@ but a custom executor can return any Future. If the method return type is not compatible with the executor the flow will run on the caller's thread and the flow must return an appropriate Future. Finally, you can disable the - gateway ansync handling by setting this attribute to "". This allows the downstream + gateway async handling by setting this attribute to "". This allows the downstream flow to return a Future that would otherwise have been compatible with the default executor. ]]> @@ -1446,7 +1446,7 @@ Boolean value indicating whether any payload that implements Cloneable should be cloned - prior to sending the Message to the request chanenl for acquiring the enriching data. + prior to sending the Message to the request channel for acquiring the enriching data. The cloned version would be used as the target payload for the ultimate reply. If the payload does NOT implement 'Cloneable', then setting this @@ -1800,7 +1800,7 @@ The java.util.concurrent.TimeUnit enum value. This can ONLY be used in combination with the 'fixed-delay' or 'fixed-rate' attributes. If combined with either 'cron' or a 'trigger' reference attribute, it will cause a failure. The minimal supported - granularity for a PeriodicTrigger is MILLISEONDS. Therefore, the only available options + granularity for a PeriodicTrigger is MILLISECONDS. Therefore, the only available options are MILLISECONDS and SECONDS. If this value is not provided, then any 'fixed-delay' or 'fixed-rate' value will be interpreted as MILLISECONDS by default. Basically this enum provides a convenience for SECONDS-based interval trigger values. For hourly, daily, @@ -2885,7 +2885,7 @@ - + - + - + @@ -3319,7 +3319,7 @@ If set to 'true', a MessagingException will be raised in case the channel cannot be resolved. Setting this attribute to 'false', - will cause any unresovable channels to be ignored. + will cause any unresolvable channels to be ignored. If not explicitly set, 'resolution-required' will default to 'true'. @@ -3699,7 +3699,7 @@ - Alows you to configure a Wire Tap interceptor that will send a copy of the message to a + Allows you to configure a Wire Tap interceptor that will send a copy of the message to a channel identified by 'channel' attribute. @@ -4210,6 +4210,80 @@ The list of component name patterns you want to track (e.g., tracked-components + + + + + + + + + + + + + + + + + + + + Base type for 'scatter-gather' elements. + + + + + + + + + + + + + + + + + + 'id' value: + - Identifies the underlying Spring bean definition (AbstractEndpoint) + - as MessageHandler bean alias together with suffix '.handler' + - as a RecipientListRouter bean together with suffix '.scatterer' + - as a AggregatingMessageHandler bean together with suffix '.gatherer' + + + + + + + Identifies the channel to send a message for 'auction' Scatter-Gather pattern variant. + Mutually exclusive with 'scatterer' sub-element. + + + + + + + Identifies the channel to receive reply Messages for gathering. + + + + + + + Allows to specify how long the Scatter-Gather will wait for reply Messages for gathering. + By default it will wait indefinitely. Value is specified in milliseconds. + It will be applied only if the 'gather-channel' is specified and it is some blocking channel, + e.g. 'QueueChannel'. + + + + + @@ -4374,7 +4448,7 @@ endpoint itself is a Polling Consumer for a channel with a queue. or poller + Attributes provided in either a or poller sub element. Used to take action after the transaction completes () or after the channel.send() is complete (). @@ -4431,7 +4505,7 @@ endpoint itself is a Polling Consumer for a channel with a queue. diff --git a/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherParserTests-context.xml new file mode 100644 index 0000000000..307deffc0c --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherParserTests-context.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherParserTests.java new file mode 100644 index 0000000000..ad5f876bcc --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherParserTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2014 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.scattergather.config; + +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.aggregator.AggregatingMessageHandler; +import org.springframework.integration.channel.FixedSubscriberChannel; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.ScatterGatherHandler; +import org.springframework.integration.router.RecipientListRouter; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.messaging.MessageHandler; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Artem Bilan + * @since 4.1 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class ScatterGatherParserTests { + + @Autowired + private BeanFactory beanFactory; + + @Test + public void testAuction() { + MessageHandler scatterGather = this.beanFactory.getBean("scatterGather1.handler", MessageHandler.class); + assertThat(scatterGather, instanceOf(ScatterGatherHandler.class)); + assertSame(this.beanFactory.getBean("scatterChannel"), + TestUtils.getPropertyValue(scatterGather, "scatterChannel")); + assertTrue(this.beanFactory.containsBean("scatterGather1.gatherer")); + AggregatingMessageHandler gatherer = + this.beanFactory.getBean("scatterGather1.gatherer", AggregatingMessageHandler.class); + assertSame(gatherer, TestUtils.getPropertyValue(scatterGather, "gatherer")); + + Object reaper = this.beanFactory.getBean("reaper"); + assertSame(gatherer.getMessageStore(), TestUtils.getPropertyValue(reaper, "messageGroupStore")); + } + + @Test + @SuppressWarnings("unchecked") + public void testDistribution() { + MessageHandler scatterGather = this.beanFactory.getBean("scatterGather2.handler", MessageHandler.class); + assertSame(this.beanFactory.getBean("gatherChannel"), + TestUtils.getPropertyValue(scatterGather, "gatherChannel")); + assertNotNull(TestUtils.getPropertyValue(scatterGather, "gatherEndpoint")); + assertThat(TestUtils.getPropertyValue(scatterGather, "gatherEndpoint"), instanceOf(EventDrivenConsumer.class)); + assertTrue(TestUtils.getPropertyValue(scatterGather, "gatherEndpoint.running", Boolean.class)); + assertEquals(100L, TestUtils.getPropertyValue(scatterGather, "gatherTimeout")); + + assertTrue(this.beanFactory.containsBean("myGatherer")); + Object gatherer = this.beanFactory.getBean("myGatherer"); + assertSame(gatherer, TestUtils.getPropertyValue(scatterGather, "gatherer")); + assertSame(this.beanFactory.getBean("messageStore"), TestUtils.getPropertyValue(gatherer, "messageStore")); + assertSame(gatherer, TestUtils.getPropertyValue(scatterGather, "gatherEndpoint.handler")); + + assertTrue(this.beanFactory.containsBean("myScatterer")); + Object scatterer = this.beanFactory.getBean("myScatterer"); + assertTrue(TestUtils.getPropertyValue(scatterer, "applySequence", Boolean.class)); + + Collection recipients = TestUtils.getPropertyValue(scatterer, "recipients", + Collection.class); + assertEquals(1, recipients.size()); + assertSame(this.beanFactory.getBean("distributionChannel"), recipients.iterator().next().getChannel()); + + Object scatterChannel = TestUtils.getPropertyValue(scatterGather, "scatterChannel"); + assertThat(scatterChannel, instanceOf(FixedSubscriberChannel.class)); + assertSame(scatterer, TestUtils.getPropertyValue(scatterChannel, "handler")); + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherTests-context.xml new file mode 100644 index 0000000000..94b70f9f48 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherTests-context.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherTests.java b/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherTests.java new file mode 100644 index 0000000000..3190e0e257 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/scattergather/config/ScatterGatherTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014 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.scattergather.config; + +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.gateway.RequestReplyExchanger; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Artem Bilan + * @since 4.1 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class ScatterGatherTests { + + @Autowired + private PollableChannel output; + + @Autowired + private MessageChannel inputAuction; + + @Autowired + private MessageChannel inputDistribution; + + @Autowired + private RequestReplyExchanger gateway; + + @Test + public void testAuction() { + this.inputAuction.send(new GenericMessage("foo")); + Message bestQuoteMessage = this.output.receive(10000); + assertNotNull(bestQuoteMessage); + Object payload = bestQuoteMessage.getPayload(); + assertThat(payload, instanceOf(List.class)); + assertThat(((List) payload).size(), greaterThanOrEqualTo(1)); + } + + @Test + public void testDistribution() { + this.inputDistribution.send(new GenericMessage("foo")); + Message bestQuoteMessage = this.output.receive(10000); + assertNotNull(bestQuoteMessage); + Object payload = bestQuoteMessage.getPayload(); + assertThat(payload, instanceOf(List.class)); + assertThat(((List) payload).size(), greaterThanOrEqualTo(1)); + } + + @Test + public void testGatewayScatterGather() { + Message bestQuoteMessage = this.gateway.exchange(new GenericMessage("foo")); + Object payload = bestQuoteMessage.getPayload(); + assertThat(payload, instanceOf(List.class)); + assertThat(((List) payload).size(), greaterThanOrEqualTo(1)); + } + +} diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpOutboundGatewayParser.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpOutboundGatewayParser.java index c1b778b2b0..e50b736ae8 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpOutboundGatewayParser.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/config/TcpOutboundGatewayParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -22,6 +22,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.ip.tcp.TcpOutboundGateway; /** * Parser for the <outbound-gateway> element of the integration 'jms' namespace. @@ -31,8 +32,6 @@ import org.springframework.integration.config.xml.IntegrationNamespaceUtils; */ public class TcpOutboundGatewayParser extends AbstractConsumerEndpointParser { - private static final String BASE_PACKAGE = "org.springframework.integration.ip.tcp"; - @Override protected String getInputChannelAttributeName() { return "request-channel"; @@ -40,8 +39,7 @@ public class TcpOutboundGatewayParser extends AbstractConsumerEndpointParser { @Override protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(BASE_PACKAGE + - ".TcpOutboundGateway"); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(TcpOutboundGateway.class); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, IpAdapterParserUtils.TCP_CONNECTION_FACTORY); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, @@ -52,10 +50,6 @@ public class TcpOutboundGatewayParser extends AbstractConsumerEndpointParser { IpAdapterParserUtils.REMOTE_TIMEOUT); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IpAdapterParserUtils.REPLY_TIMEOUT, "sendTimeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, - IntegrationNamespaceUtils.AUTO_STARTUP); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, - IntegrationNamespaceUtils.PHASE); return builder; } diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/TcpOutboundGateway.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/TcpOutboundGateway.java index 5e1e093928..e5ed9691ed 100644 --- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/TcpOutboundGateway.java +++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/TcpOutboundGateway.java @@ -22,6 +22,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import org.springframework.context.Lifecycle; import org.springframework.context.SmartLifecycle; import org.springframework.integration.MessageTimeoutException; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; @@ -50,7 +51,8 @@ import org.springframework.util.Assert; * @author Gary Russell * @since 2.0 */ -public class TcpOutboundGateway extends AbstractReplyProducingMessageHandler implements TcpSender, TcpListener, SmartLifecycle { +public class TcpOutboundGateway extends AbstractReplyProducingMessageHandler + implements TcpSender, TcpListener, Lifecycle { private volatile AbstractClientConnectionFactory connectionFactory; @@ -64,10 +66,6 @@ public class TcpOutboundGateway extends AbstractReplyProducingMessageHandler imp private volatile long requestTimeout = 10000; - private volatile boolean autoStartup = true; - - private volatile int phase; - /** * @param requestTimeout the requestTimeout to set */ @@ -232,29 +230,6 @@ public class TcpOutboundGateway extends AbstractReplyProducingMessageHandler imp return this.connectionFactory.isRunning(); } - @Override - public int getPhase() { - return this.phase; - } - - @Override - public boolean isAutoStartup() { - return this.autoStartup; - } - - @Override - public void stop(Runnable callback) { - this.connectionFactory.stop(callback); - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - public void setPhase(int phase) { - this.phase = phase; - } - /** * @return the connectionFactory */ diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests.java b/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests.java index 81398e8be2..c951164452 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests.java +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests.java @@ -496,8 +496,6 @@ public class ParserUnitTests { assertEquals("ip:tcp-outbound-gateway", tcpOutboundGateway.getComponentType()); assertTrue(cfC2.isLookupHost()); assertEquals(24, dfa.getPropertyValue("order")); - assertFalse(tcpOutboundGateway.isAutoStartup()); - assertEquals(127, tcpOutboundGateway.getPhase()); } @Test diff --git a/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java b/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java index 582602eedb..e249bb8195 100644 --- a/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java +++ b/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java @@ -42,7 +42,7 @@ import javax.jms.TemporaryQueue; import javax.jms.TemporaryTopic; import javax.jms.Topic; -import org.springframework.context.SmartLifecycle; +import org.springframework.context.Lifecycle; import org.springframework.expression.Expression; import org.springframework.integration.IntegrationMessageHeaderAccessor; import org.springframework.integration.MessageTimeoutException; @@ -74,7 +74,7 @@ import org.springframework.util.StringUtils; * @author Gary Russell * @author Artem Bilan */ -public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler implements SmartLifecycle, MessageListener { +public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler implements Lifecycle, MessageListener { private volatile Destination requestDestination; @@ -126,8 +126,6 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp private final Object initializationMonitor = new Object(); - private volatile boolean autoStartup; - private volatile boolean active; private final AtomicLong correlationId = new AtomicLong(); @@ -484,20 +482,6 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp session, replyDestinationName, this.replyPubSubDomain); } - @Override - public int getPhase() { - return Integer.MAX_VALUE; - } - - @Override - public boolean isAutoStartup() { - return this.autoStartup; - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - @Override protected void doInit() { synchronized (this.initializationMonitor) { @@ -663,12 +647,6 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler imp return this.active; } - @Override - public void stop(Runnable callback) { - this.stop(); - callback.run(); - } - @Override protected Object handleRequestMessage(final Message message) { if (!this.initialized) { diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ScatterGatherHandlerIntegrationTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ScatterGatherHandlerIntegrationTests.java new file mode 100644 index 0000000000..5219a2f69d --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ScatterGatherHandlerIntegrationTests.java @@ -0,0 +1,361 @@ +/* + * Copyright 2014 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.monitor; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Executors; + +import javax.management.MBeanServer; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.integration.aggregator.AggregatingMessageHandler; +import org.springframework.integration.aggregator.DefaultAggregatingMessageGroupProcessor; +import org.springframework.integration.aggregator.ExpressionEvaluatingMessageGroupProcessor; +import org.springframework.integration.aggregator.ExpressionEvaluatingReleaseStrategy; +import org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy; +import org.springframework.integration.aggregator.MessageCountReleaseStrategy; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.PublishSubscribeChannel; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.handler.BridgeHandler; +import org.springframework.integration.handler.ScatterGatherHandler; +import org.springframework.integration.jmx.config.EnableIntegrationMBeanExport; +import org.springframework.integration.router.RecipientListRouter; +import org.springframework.integration.store.SimpleMessageStore; +import org.springframework.jmx.support.MBeanServerFactoryBean; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Artem Bilan + * @since 4.1 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext +public class ScatterGatherHandlerIntegrationTests { + + @Autowired + private PollableChannel output; + + @Autowired + private MessageChannel inputAuctionWithoutGatherChannel; + + @Autowired + private MessageChannel inputAuctionWithGatherChannel; + + @Autowired + private MessageChannel distributionChannel; + + @Test + public void testSimpleAuction() { + Message quoteMessage = MessageBuilder.withPayload("testQuote").build(); + + this.inputAuctionWithoutGatherChannel.send(quoteMessage); + Message bestQuoteMessage = this.output.receive(10000); + assertNotNull(bestQuoteMessage); + Object payload = bestQuoteMessage.getPayload(); + assertThat(payload, instanceOf(Double.class)); + assertThat((Double) payload, lessThan(10D)); + } + + @Test + public void testAuctionWithGatherChannel() { + Message quoteMessage = MessageBuilder.withPayload("testQuote").build(); + + this.inputAuctionWithGatherChannel.send(quoteMessage); + Message bestQuoteMessage = this.output.receive(10000); + assertNotNull(bestQuoteMessage); + Object payload = bestQuoteMessage.getPayload(); + assertThat(payload, instanceOf(List.class)); + assertEquals(3, ((List) payload).size()); + } + + @Test + public void testDistribution() { + Message quoteMessage = MessageBuilder.withPayload("testQuote").build(); + + this.distributionChannel.send(quoteMessage); + Message bestQuoteMessage = this.output.receive(10000); + assertNotNull(bestQuoteMessage); + Object payload = bestQuoteMessage.getPayload(); + assertThat(payload, instanceOf(Double.class)); + assertThat((Double) payload, lessThan(10D)); + } + + + @Configuration + @EnableIntegration + @EnableIntegrationMBeanExport(server = "mBeanServer") + public static class ContextConfiguration { + + @Bean + public static MBeanServerFactoryBean mBeanServer() { + return new MBeanServerFactoryBean(); + } + + @Bean + public PollableChannel output() { + return new QueueChannel(); + } + + @Bean + public SubscribableChannel scatterAuctionWithoutGatherChannel() { + PublishSubscribeChannel channel = new PublishSubscribeChannel(); + channel.setApplySequence(true); + return channel; + } + + @Bean + public MessageHandler gatherer1() { + return new AggregatingMessageHandler( + new ExpressionEvaluatingMessageGroupProcessor("^[payload gt 5] ?: -1D"), + new SimpleMessageStore(), + new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID), + new ExpressionEvaluatingReleaseStrategy("size() == 2")); + } + + @Bean + public MessageChannel inputAuctionWithoutGatherChannel() { + return new DirectChannel(); + } + + @Bean + @ServiceActivator(inputChannel = "inputAuctionWithoutGatherChannel") + public MessageHandler scatterGatherAuctionWithoutGatherChannel() { + ScatterGatherHandler handler = new ScatterGatherHandler(scatterAuctionWithoutGatherChannel(), gatherer1()); + handler.setOutputChannel(output()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel") + public MessageHandler auctionWithoutGatherChannelBridge1() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel1()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel") + public MessageHandler auctionWithoutGatherChannelBridge2() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel1()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "scatterAuctionWithoutGatherChannel") + public MessageHandler auctionWithoutGatherChannelBridge3() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel1()); + return handler; + } + + @Bean + public MessageChannel serviceChannel1() { + return new DirectChannel(); + } + + @Bean + @ServiceActivator(inputChannel = "serviceChannel1") + public MessageHandler service1() { + return new AbstractReplyProducingMessageHandler() { + + @Override + protected Object handleRequestMessage(Message requestMessage) { + return Math.random() * 10; + } + + }; + } + + @Bean + public MessageHandler distributor() { + RecipientListRouter router = new RecipientListRouter(); + router.setApplySequence(true); + router.setChannels(Arrays.asList(distributionChannel1(), distributionChannel2(), distributionChannel3())); + return router; + } + + @Bean + public MessageChannel distributionChannel1() { + return new DirectChannel(); + } + + @Bean + public MessageChannel distributionChannel2() { + return new DirectChannel(); + } + + @Bean + public MessageChannel distributionChannel3() { + return new DirectChannel(); + } + + @Bean + public MessageChannel distributionChannel() { + return new DirectChannel(); + } + + @Bean + @ServiceActivator(inputChannel = "distributionChannel") + public MessageHandler scatterGatherDistribution() { + ScatterGatherHandler handler = new ScatterGatherHandler(distributor(), gatherer1()); + handler.setOutputChannel(output()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "distributionChannel1") + public MessageHandler distributionBridge1() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel1()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "distributionChannel2") + public MessageHandler distributionBridge2() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel1()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "distributionChannel3") + public MessageHandler distributionBridge3() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel1()); + return handler; + } + + @Bean + public MessageChannel inputAuctionWithGatherChannel() { + return new DirectChannel(); + } + + @Bean + public MessageChannel gatherChannel() { + return new DirectChannel(); + } + + @Bean + public SubscribableChannel scatterAuctionWithGatherChannel() { + PublishSubscribeChannel channel = new PublishSubscribeChannel(Executors.newCachedThreadPool()); + channel.setApplySequence(true); + return channel; + } + + @Bean + public MessageHandler gatherer2() { + return new AggregatingMessageHandler(new DefaultAggregatingMessageGroupProcessor(), + new SimpleMessageStore(), + new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID), + new MessageCountReleaseStrategy(3)); + } + + + @Bean + @ServiceActivator(inputChannel = "inputAuctionWithGatherChannel") + public MessageHandler scatterGatherAuctionWithGatherChannel() { + ScatterGatherHandler handler = new ScatterGatherHandler(scatterAuctionWithGatherChannel(), gatherer2()); + handler.setGatherChannel(gatherChannel()); + handler.setOutputChannel(output()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel") + public MessageHandler auctionWithGatherChannelBridge1() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel2()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel") + public MessageHandler auctionWithGatherChannelBridge2() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel2()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel") + public MessageHandler auctionWithGatherChannelBridge3() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel2()); + return handler; + } + + @Bean + @ServiceActivator(inputChannel = "scatterAuctionWithGatherChannel") + public MessageHandler auctionWithGatherChannelBridge4() { + BridgeHandler handler = new BridgeHandler(); + handler.setOutputChannel(serviceChannel2()); + return handler; + } + + @Bean + public MessageChannel serviceChannel2() { + return new DirectChannel(); + } + + @Bean + @ServiceActivator(inputChannel = "serviceChannel2") + public MessageHandler service2() { + return new AbstractReplyProducingMessageHandler() { + + { + setOutputChannel(gatherChannel()); + } + + @Override + protected Object handleRequestMessage(Message requestMessage) { + return Math.random(); + } + + }; + } + + } + +} diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttParserUtils.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttParserUtils.java index 7489554e35..41388831b8 100644 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttParserUtils.java +++ b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/config/xml/MqttParserUtils.java @@ -57,8 +57,6 @@ public final class MqttParserUtils { } } IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "converter"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "phase"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); } diff --git a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/AbstractMqttMessageHandler.java b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/AbstractMqttMessageHandler.java index e8a508d827..23ce4c4512 100644 --- a/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/AbstractMqttMessageHandler.java +++ b/spring-integration-mqtt/src/main/java/org/springframework/integration/mqtt/outbound/AbstractMqttMessageHandler.java @@ -16,7 +16,7 @@ package org.springframework.integration.mqtt.outbound; -import org.springframework.context.SmartLifecycle; +import org.springframework.context.Lifecycle; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; import org.springframework.integration.mqtt.support.MqttHeaders; @@ -32,7 +32,7 @@ import org.springframework.util.Assert; * @since 4.0 * */ -public abstract class AbstractMqttMessageHandler extends AbstractMessageHandler implements SmartLifecycle { +public abstract class AbstractMqttMessageHandler extends AbstractMessageHandler implements Lifecycle { private final String url; @@ -48,10 +48,6 @@ public abstract class AbstractMqttMessageHandler extends AbstractMessageHandler private boolean running; - private volatile int phase; - - private volatile boolean autoStartup; - private volatile int clientInstance; public AbstractMqttMessageHandler(String url, String clientId) { @@ -118,6 +114,7 @@ public abstract class AbstractMqttMessageHandler extends AbstractMessageHandler @Override public final void start() { this.doStart(); + this.running = true; } protected abstract void doStart(); @@ -125,6 +122,7 @@ public abstract class AbstractMqttMessageHandler extends AbstractMessageHandler @Override public final void stop() { this.doStop(); + this.running = false; } protected abstract void doStop(); @@ -134,30 +132,6 @@ public abstract class AbstractMqttMessageHandler extends AbstractMessageHandler return this.running; } - @Override - public int getPhase() { - return this.phase; - } - - public void setPhase(int phase) { - this.phase = phase; - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - @Override - public boolean isAutoStartup() { - return this.autoStartup; - } - - @Override - public void stop(Runnable callback) { - this.stop(); - callback.run(); - } - @Override protected void handleMessageInternal(Message message) throws Exception { this.connectIfNeeded(); diff --git a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests.java b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests.java index 0b075c18b1..89d494825c 100644 --- a/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests.java +++ b/spring-integration-mqtt/src/test/java/org/springframework/integration/mqtt/config/xml/MqttOutboundChannelAdapterParserTests.java @@ -68,8 +68,6 @@ public class MqttOutboundChannelAdapterParserTests { @Test public void testWithConverter() throws Exception { assertEquals("tcp://localhost:1883", TestUtils.getPropertyValue(withConverterHandler, "url")); - assertFalse(TestUtils.getPropertyValue(withConverterHandler, "autoStartup", Boolean.class)); - assertEquals(25, TestUtils.getPropertyValue(withConverterHandler, "phase")); assertEquals("foo", TestUtils.getPropertyValue(withConverterHandler, "clientId")); assertEquals("bar", TestUtils.getPropertyValue(withConverterHandler, "defaultTopic")); assertSame(converter, TestUtils.getPropertyValue(withConverterHandler, "converter")); @@ -90,8 +88,6 @@ public class MqttOutboundChannelAdapterParserTests { @Test public void testWithDefaultConverter() { assertEquals("tcp://localhost:1883", TestUtils.getPropertyValue(withDefaultConverterHandler, "url")); - assertFalse(TestUtils.getPropertyValue(withDefaultConverterHandler, "autoStartup", Boolean.class)); - assertEquals(25, TestUtils.getPropertyValue(withDefaultConverterHandler, "phase")); assertEquals("foo", TestUtils.getPropertyValue(withDefaultConverterHandler, "clientId")); assertEquals("bar", TestUtils.getPropertyValue(withDefaultConverterHandler, "defaultTopic")); assertEquals(1, TestUtils.getPropertyValue(withDefaultConverterHandler, "defaultQos")); diff --git a/src/reference/docbook/message-routing.xml b/src/reference/docbook/message-routing.xml index b71b571da3..62c84c00f6 100644 --- a/src/reference/docbook/message-routing.xml +++ b/src/reference/docbook/message-routing.xml @@ -10,5 +10,6 @@ + diff --git a/src/reference/docbook/scatter-gather.xml b/src/reference/docbook/scatter-gather.xml new file mode 100644 index 0000000000..52b603d3d5 --- /dev/null +++ b/src/reference/docbook/scatter-gather.xml @@ -0,0 +1,26 @@ + +
+ Scatter-Gather + +
+ Introduction + + TBD + +
+ +
+ Functionality + + TBD + +
+ +
+ Configuring a Scatter-Gather + + TBD + +
+ +
diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index d0bd1573cb..d0baec2766 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -27,6 +27,13 @@ See for more information. +
+ Scatter-Gather EIP pattern + + The Scatter-Gather EIP pattern is now implemented. + See for more information. + +
BoonJsonObjectMapper