diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DelayerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DelayerParser.java
index 037be313b5..192e4231d1 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DelayerParser.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DelayerParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.integration.config.xml;
+import org.springframework.integration.handler.DelayHandler;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -24,30 +25,49 @@ import org.springframework.util.StringUtils;
/**
* Parser for the <delayer> element.
- *
+ *
* @author Mark Fisher
+ * @author Artem Bilan
* @since 1.0.3
*/
public class DelayerParser extends AbstractConsumerEndpointParser {
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
- BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
- IntegrationNamespaceUtils.BASE_PACKAGE + ".handler.DelayHandler");
- String defaultDelay = element.getAttribute("default-delay");
- if (!StringUtils.hasText(defaultDelay)) {
- parserContext.getReaderContext().error("The 'default-delay' attribute is required.", element);
- return null;
+ BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DelayHandler.class);
+
+ String id = element.getAttribute(ID_ATTRIBUTE);
+ if (!StringUtils.hasText(id)) {
+ parserContext.getReaderContext().error("The 'id' attribute is required.", element);
}
- builder.addConstructorArgValue(defaultDelay);
+
+ String defaultDelay = element.getAttribute("default-delay");
+ String delayHeaderName = element.getAttribute("delay-header-name");
+
+ boolean hasDefaultDelay = StringUtils.hasText(defaultDelay);
+ boolean hasDelayHeaderName = StringUtils.hasText(delayHeaderName);
+
+ if (!(hasDefaultDelay | hasDelayHeaderName)) {
+ parserContext.getReaderContext()
+ .error("The 'default-delay' or 'delay-header-name' attributes should be provided.", element);
+ }
+
+ builder.addConstructorArgValue(id + ".messageGroupId");
+
String scheduler = element.getAttribute("scheduler");
if (StringUtils.hasText(scheduler)) {
builder.addConstructorArgReference(scheduler);
}
+
+ if (hasDefaultDelay) {
+ builder.addPropertyValue("defaultDelay", defaultDelay);
+ }
+ if (hasDelayHeaderName) {
+ builder.addPropertyValue("delayHeaderName", delayHeaderName);
+ }
+
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-store");
- IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "delay-header-name");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout");
- IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "wait-for-tasks-to-complete-on-shutdown");
return builder;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java
index 9889f5d649..756bab80ad 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,31 +16,21 @@
package org.springframework.integration.handler;
+import java.io.Serializable;
import java.util.Date;
-import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.core.Ordered;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.integration.Message;
-import org.springframework.integration.MessageChannel;
-import org.springframework.integration.MessageHandlingException;
-import org.springframework.integration.MessageHeaders;
-import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.core.MessageHandler;
-import org.springframework.integration.core.MessageProducer;
-import org.springframework.integration.core.MessagingTemplate;
-import org.springframework.integration.message.ErrorMessage;
+import org.springframework.integration.store.MessageGroup;
+import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
-import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
-import org.springframework.integration.support.channel.ChannelResolutionException;
-import org.springframework.integration.support.channel.ChannelResolver;
+import org.springframework.integration.support.MessageBuilder;
import org.springframework.scheduling.TaskScheduler;
-import org.springframework.scheduling.concurrent.ExecutorConfigurationSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
@@ -52,12 +42,12 @@ import org.springframework.util.Assert;
* therefore, the calling thread does not block. The advantage of this approach
* is that many delays can be managed concurrently, even very long delays,
* without producing a buildup of blocked Threads.
- *
{
- private final Log logger = LogFactory.getLog(this.getClass());
+ private final String messageGroupId;
private volatile long defaultDelay;
private volatile String delayHeaderName;
- private boolean waitForTasksToCompleteOnShutdown = false;
-
- private volatile MessageChannel outputChannel;
-
- private volatile ChannelResolver channelResolver;
-
- private volatile MessageStore messageStore;
-
- private final MessagingTemplate messagingTemplate = new MessagingTemplate();
-
- private volatile int order = Ordered.LOWEST_PRECEDENCE;
+ private volatile MessageGroupStore messageStore;
+ private final AtomicBoolean initialized = new AtomicBoolean();
/**
- * Create a DelayHandler with the given default delay. The sending of Messages after
- * the delay will be handled by a scheduled thread pool with a size of 1.
+ * Create a DelayHandler with the given 'messageGroupId' that is used as 'key' for {@link MessageGroup}
+ * to store delayed Messages in the {@link MessageGroupStore}. The sending of Messages after
+ * the delay will be handled by registered in the ApplicationContext default {@link ThreadPoolTaskScheduler}.
+ *
+ * @see IntegrationObjectSupport#getTaskScheduler()
*/
- public DelayHandler(long defaultDelay) {
- this(defaultDelay, null);
+ public DelayHandler(String messageGroupId) {
+ Assert.notNull(messageGroupId, "'messageGroupId' must not be null");
+ this.messageGroupId = messageGroupId;
}
/**
* Create a DelayHandler with the given default delay. The sending of Messages
* after the delay will be handled by the provided {@link TaskScheduler}.
*/
- public DelayHandler(long defaultDelay, TaskScheduler taskScheduler) {
- this.defaultDelay = defaultDelay;
- this.setTaskScheduler(taskScheduler != null ? taskScheduler : new ThreadPoolTaskScheduler());
+ public DelayHandler(String messageGroupId, TaskScheduler taskScheduler) {
+ this(messageGroupId);
+ this.setTaskScheduler(taskScheduler);
}
-
/**
* Set the default delay in milliseconds. If no 'delayHeaderName' property
* has been provided, the default delay will be applied to all Messages. If
* a delay should only be applied to Messages with a
- * header, then set this value to 0.
+ * header, then set this value to 0.
*/
public void setDefaultDelay(long defaultDelay) {
this.defaultDelay = defaultDelay;
@@ -130,82 +115,57 @@ public class DelayHandler extends IntegrationObjectSupport implements MessageHan
}
/**
- * Specify the {@link MessageStore} that should be used to store Messages
+ * Specify the {@link MessageGroupStore} that should be used to store Messages
* while awaiting the delay.
*/
- public void setMessageStore(MessageStore messageStore) {
+ public void setMessageStore(MessageGroupStore messageStore) {
+ Assert.state(messageStore != null, "MessageStore must not be null");
this.messageStore = messageStore;
}
- /**
- * Set the output channel for this handler. If none is provided, each
- * inbound Message must include a reply channel header.
- */
- public void setOutputChannel(MessageChannel outputChannel) {
- this.outputChannel = outputChannel;
- }
-
- /**
- * Set the timeout for sending reply Messages.
- */
- public void setSendTimeout(long sendTimeout) {
- this.messagingTemplate.setSendTimeout(sendTimeout);
- }
-
- /**
- * Set whether to wait for scheduled tasks to complete on shutdown.
- * Default is "false". Switch this to "true" if you prefer
- * fully completed tasks at the expense of a longer shutdown phase.
- *
- * This property will only have an effect for TaskScheduler implementations
- * that extend from {@link ExecutorConfigurationSupport}.
- * @see ExecutorConfigurationSupport#setWaitForTasksToCompleteOnShutdown(boolean)
- */
- public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
- this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
- }
-
- public void setOrder(int order) {
- this.order = order;
- }
-
- public int getOrder() {
- return this.order;
- }
-
@Override
public String getComponentType() {
return "delayer";
}
- protected void onInit() throws Exception{
- if (this.getTaskScheduler() instanceof ExecutorConfigurationSupport) {
- ((ExecutorConfigurationSupport) this.getTaskScheduler()).setWaitForTasksToCompleteOnShutdown(this.waitForTasksToCompleteOnShutdown);
- }
- else if (logger.isWarnEnabled()) {
- logger.warn("The 'waitForJobsToCompleteOnShutdown' property is not supported for TaskScheduler of type [" +
- this.getTaskScheduler().getClass() + "]");
- }
+ @Override
+ protected void onInit() {
+ super.onInit();
if (this.messageStore == null) {
this.messageStore = new SimpleMessageStore();
}
- if (this.getTaskScheduler() instanceof InitializingBean) {
- ((InitializingBean) this.getTaskScheduler()).afterPropertiesSet();
- }
- if (this.getBeanFactory() != null){
- this.channelResolver = new BeanFactoryChannelResolver(this.getBeanFactory());
+ else {
+ Assert.isInstanceOf(MessageStore.class, this.messageStore);
}
}
- public final void handleMessage(final Message> message) {
- long delay = this.determineDelayForMessage(message);
- if (delay > 0) {
- this.releaseMessageAfterDelay(message, delay);
- }
- else {
- // no delay, send directly
- this.sendMessageToReplyChannel(message);
+ /**
+ * Checks if 'requestMessage' wasn't delayed before
+ * ({@link #releaseMessageAfterDelay} and {@link DelayedMessageWrapper}).
+ * Than determine 'delay' for 'requestMessage' ({@link #determineDelayForMessage})
+ * and if delay > 0 schedules 'releaseMessage' task after 'delay'.
+ *
+ * @param requestMessage - the Message which may be delayed.
+ * @return - null if 'requestMessage' is delayed,
+ * otherwise - 'payload' from 'requestMessage'.
+ * @see #releaseMessage
+ */
+
+ @Override
+ protected Object handleRequestMessage(Message> requestMessage) {
+ boolean delayed = requestMessage.getPayload() instanceof DelayedMessageWrapper;
+
+ if (!delayed) {
+ long delay = this.determineDelayForMessage(requestMessage);
+ if (delay > 0) {
+ this.releaseMessageAfterDelay(requestMessage, delay);
+ return null;
+ }
}
+
+ // no delay
+ Object payload = requestMessage.getPayload();
+ return delayed ? ((DelayedMessageWrapper) payload).getOriginal().getPayload() : payload;
}
private long determineDelayForMessage(Message> message) {
@@ -231,98 +191,116 @@ public class DelayHandler extends IntegrationObjectSupport implements MessageHan
}
private void releaseMessageAfterDelay(final Message> message, long delay) {
- Assert.state(this.messageStore != null, "MessageStore must not be null");
- final Message> storedMessage = this.messageStore.addMessage(message);
+ Message> delayedMessage = message;
+
+ DelayedMessageWrapper messageWrapper = null;
+ if (message.getPayload() instanceof DelayedMessageWrapper) {
+ messageWrapper = (DelayedMessageWrapper) message.getPayload();
+ }
+ else {
+ messageWrapper = new DelayedMessageWrapper(message);
+ delayedMessage = MessageBuilder.withPayload(messageWrapper).copyHeaders(message.getHeaders()).build();
+ this.messageStore.addMessageToGroup(this.messageGroupId, delayedMessage);
+ }
+
+ final Message> messageToSchedule = delayedMessage;
+
this.getTaskScheduler().schedule(new Runnable() {
public void run() {
- try {
- releaseMessage(storedMessage.getHeaders().getId());
- }
- catch (Exception e) {
- Exception exception = new MessageHandlingException(message, "Failed to deliver Message after delay.", e);
- MessageChannel errorChannel = resolveErrorChannelIfPossible(message);
- if (errorChannel != null) {
- ErrorMessage errorMessage = new ErrorMessage(exception);
- try {
- messagingTemplate.send(errorChannel, errorMessage);
- }
- catch (Exception e2) {
- if (logger.isWarnEnabled()) {
- logger.warn("Failed to send MessagingException to error channel.", exception);
- }
- }
+ releaseMessage(messageToSchedule);
+ }
+ }, new Date(messageWrapper.getRequestDate() + delay));
+ }
+
+ private void releaseMessage(Message> message) {
+ if (this.messageStore instanceof SimpleMessageStore
+ || ((MessageStore) this.messageStore).removeMessage(message.getHeaders().getId()) != null) {
+ this.messageStore.removeMessageFromGroup(this.messageGroupId, message);
+ this.handleMessageInternal(message);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("No message in the Message Store to release: " + message +
+ ". Likely another instance has already released it.");
+ }
+ }
+ }
+
+ /**
+ * Used for reading persisted Messages in the 'messageStore'
+ * to reschedule them e.g. upon application restart.
+ * The logic is based on iteration over 'messageGroup.getMessages()'
+ * and schedules task about 'delay' logic.
+ * This behavior is dictated by the avoidance of invocation thread overload.
+ */
+ public void reschedulePersistedMessages() {
+ MessageGroup messageGroup = this.messageStore.getMessageGroup(this.messageGroupId);
+ for (final Message> message : messageGroup.getMessages()) {
+ this.getTaskScheduler().schedule(new Runnable() {
+ public void run() {
+ long delay = determineDelayForMessage(message);
+ if (delay > 0) {
+ releaseMessageAfterDelay(message, delay);
}
- else if (logger.isWarnEnabled()) {
- logger.warn("No error channel available. MessagingException will be ignored.", exception);
+ else {
+ releaseMessage(message);
}
}
- }
- }, new Date(System.currentTimeMillis() + delay));
+ }, new Date());
+ }
}
- private void releaseMessage(UUID id) {
- Assert.state(this.messageStore != null, "MessageStore must not be null");
- Message> message = this.messageStore.removeMessage(id);
- Assert.notNull(message, "Message with id: " + id + " no longer exists in MessageStore.");
- this.sendMessageToReplyChannel(message);
+ /**
+ * Handles {@link ContextRefreshedEvent} to invoke {@link #reschedulePersistedMessages}
+ * as late as possible after application context startup.
+ * Also it checks {@link #initialized} to ignore
+ * other {@link ContextRefreshedEvent}s which may be published
+ * in the 'parent-child' contexts, e.g. in the Spring-MVC applications.
+ *
+ * @param event - {@link ContextRefreshedEvent} which occurs
+ * after Application context is completely initialized.
+ * @see #reschedulePersistedMessages
+ */
+ public void onApplicationEvent(ContextRefreshedEvent event) {
+ if (!this.initialized.getAndSet(true)) {
+ this.reschedulePersistedMessages();
+ }
}
- private void sendMessageToReplyChannel(Message> message) {
- MessageChannel replyChannel = this.resolveReplyChannel(message);
- this.messagingTemplate.send(replyChannel, message);
+ private static final class DelayedMessageWrapper implements Serializable {
+
+ private static final long serialVersionUID = -4739802369074947045L;
+
+ private final long requestDate = System.currentTimeMillis();
+
+ private final Message> original;
+
+ public DelayedMessageWrapper(Message> original) {
+ this.original = original;
+ }
+
+ public long getRequestDate() {
+ return this.requestDate;
+ }
+
+ public Message> getOriginal() {
+ return this.original;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ DelayedMessageWrapper that = (DelayedMessageWrapper) o;
+
+ return this.original.equals(that.original);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.original.hashCode();
+ }
}
- private MessageChannel resolveReplyChannel(Message> message) {
- MessageChannel replyChannel = this.outputChannel;
- if (replyChannel == null) {
- replyChannel = this.resolveChannelFromHeader(message, MessageHeaders.REPLY_CHANNEL);
- }
- if (replyChannel == null) {
- throw new ChannelResolutionException(
- "unable to resolve reply channel for message: " + message);
- }
- return replyChannel;
- }
-
- private MessageChannel resolveErrorChannelIfPossible(Message> message) {
- MessageChannel errorChannel = null;
- try {
- errorChannel = this.resolveChannelFromHeader(message, MessageHeaders.ERROR_CHANNEL);
- }
- catch (Exception e) {
- if (logger.isWarnEnabled()) {
- logger.warn("Failed to resolve error channel from header.", e);
- }
- }
- if (errorChannel == null && this.channelResolver != null) {
- errorChannel = this.channelResolver.resolveChannelName(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME);
- }
- return errorChannel;
- }
-
- private MessageChannel resolveChannelFromHeader(Message> message, String headerName) {
- MessageChannel channel = null;
- Object channelHeader = message.getHeaders().get(headerName);
- if (channelHeader != null) {
- if (channelHeader instanceof MessageChannel) {
- channel = (MessageChannel) channelHeader;
- }
- else if (channelHeader instanceof String) {
- Assert.state(this.channelResolver != null,
- "ChannelResolver is required for resolving '" + headerName + "' by name.");
- channel = this.channelResolver.resolveChannelName((String) channelHeader);
- }
- else {
- throw new ChannelResolutionException("expected a MessageChannel or String for '" +
- headerName + "', but type is [" + channelHeader.getClass() + "]");
- }
- }
- return channel;
- }
-
- public void destroy() throws Exception {
- if (this.getTaskScheduler() instanceof DisposableBean) {
- ((DisposableBean) this.getTaskScheduler()).destroy();
- }
- }
}
diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.2.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.2.xsd
index 5c3ce54be6..cc92ba353a 100644
--- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.2.xsd
+++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.2.xsd
@@ -716,7 +716,7 @@
-
+
@@ -770,11 +770,11 @@
-
+
- Reference to a org.springframework.core.io.support.ResourcePatternResolver.
+ Reference to a org.springframework.core.io.support.ResourcePatternResolver.
@@ -986,7 +986,7 @@
-
+
@@ -1085,7 +1085,7 @@
-
+
@@ -1138,7 +1138,7 @@
@@ -1147,7 +1147,7 @@
@@ -1156,25 +1156,25 @@
-
+
@@ -1268,17 +1268,28 @@
-
+
+
+
+
-
-
-
-
+
+
+
+ 'id' value is used:
+ - as the 'AbstractEndpoint' bean 'id' if this tag is defined as root element.
+ - as the DelayHandler bean alias together with suffix '.handler'
+ - as the 'messageGroupId' property of DelayHandler together with suffix '.messageGroupId'
+ in the operations of the MessageGroupStore for scheduling delayed messages.
+
+
+
+
Specify the default delay in milliseconds. This value can be set to 0
@@ -1309,10 +1320,9 @@
Provide a reference to the TaskScheduler instance to which
this endpoint should
delegate when scheduling the sending of delayed Messages. If not
- provided, the default
- will use a thread pool of
- size
- 1.
+ provided, the default scheduler
+ registered in the ApplicationContext {ThreadPoolTaskScheduler} will be
+ used.
@@ -1335,14 +1345,6 @@
-
-
-
- Specify whether tasks should be able to complete on shutdown. By
- default this is 'false'.
-
-
-
@@ -1358,7 +1360,7 @@
-
+
@@ -1372,7 +1374,7 @@
-
+
@@ -1635,7 +1637,7 @@
-
+
@@ -1895,7 +1897,7 @@
-
+
@@ -1934,7 +1936,7 @@
-
+
@@ -1951,7 +1953,7 @@
-
+
@@ -1965,7 +1967,7 @@
-
+
@@ -1979,7 +1981,7 @@
-
+
@@ -2021,7 +2023,7 @@
-
+
@@ -2065,7 +2067,7 @@
-
+
@@ -2107,7 +2109,7 @@
-
+
@@ -2142,7 +2144,7 @@
-
+
@@ -2185,7 +2187,7 @@
-
+
@@ -2213,7 +2215,7 @@
-
+
@@ -2279,7 +2281,7 @@
-
+
@@ -2987,7 +2989,7 @@ is provided, the return value is expected to match a channel name exactly.
-
+
@@ -3037,7 +3039,7 @@ is provided, the return value is expected to match a channel name exactly.
-
+
@@ -3188,7 +3190,7 @@ is provided, the return value is expected to match a channel name exactly.
-
+
@@ -3604,7 +3606,7 @@ The list of component name patterns you want to track (e.g., tracked-components
-
+
@@ -3625,7 +3627,6 @@ The list of component name patterns you want to track (e.g., tracked-components
-
@@ -3681,4 +3682,18 @@ endpoint itself is a Polling Consumer for a channel with a queue.
+
+
+
+
+
+ 'id' value:
+ - Identifies the underlying Spring bean definition (AbstractEndpoint)
+ - as MessageHandler bean alias together with suffix '.handler'
+
+
+
+
+
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests-context.xml
index cecafcd54b..1784af583a 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests-context.xml
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests-context.xml
@@ -2,11 +2,9 @@
@@ -22,8 +20,7 @@
default-delay="1234"
delay-header-name="foo"
order="99"
- send-timeout="987"
- wait-for-tasks-to-complete-on-shutdown="true"/>
+ send-timeout="987"/>
-
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java
index bad07009f4..e9d0b4f856 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerParserTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
package org.springframework.integration.config.xml;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +35,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
+ * @author Artem Bilan
* @since 1.0.3
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -57,8 +60,7 @@ public class DelayerParserTests {
assertEquals("foo", accessor.getPropertyValue("delayHeaderName"));
assertEquals(new Long(987), new DirectFieldAccessor(
accessor.getPropertyValue("messagingTemplate")).getPropertyValue("sendTimeout"));
- assertEquals(Boolean.TRUE, new DirectFieldAccessor(
- accessor.getPropertyValue("taskScheduler")).getPropertyValue("waitForTasksToCompleteOnShutdown"));
+ assertNull(accessor.getPropertyValue("taskScheduler"));
}
@Test
@@ -73,6 +75,9 @@ public class DelayerParserTests {
assertEquals(context.getBean("output"), accessor.getPropertyValue("outputChannel"));
assertEquals(new Long(0), accessor.getPropertyValue("defaultDelay"));
assertEquals(context.getBean("testScheduler"), accessor.getPropertyValue("taskScheduler"));
+ assertNotNull(accessor.getPropertyValue("taskScheduler"));
+ assertEquals(Boolean.TRUE, new DirectFieldAccessor(
+ accessor.getPropertyValue("taskScheduler")).getPropertyValue("waitForTasksToCompleteOnShutdown"));
}
@Test
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerUsageTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerUsageTests-context.xml
index 5e911655f4..a24a2826cb 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerUsageTests-context.xml
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerUsageTests-context.xml
@@ -2,11 +2,9 @@
@@ -22,26 +20,33 @@
default-delay="1000"
delay-header-name="foo"
order="99"
- send-timeout="1000"
- wait-for-tasks-to-complete-on-shutdown="true"/>
+ send-timeout="1000"/>
-
+
+ scheduler="multiThreadScheduler"/>
+
+
+
+
+
+
+
+
-
-
-
+
-
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerUsageTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerUsageTests.java
index fd82531b9a..09cd1fed3a 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerUsageTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/DelayerUsageTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,13 +16,15 @@
package org.springframework.integration.config.xml;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.message.GenericMessage;
@@ -32,64 +34,81 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Oleg Zhurakousky
+ * @author Artem Bilan
* @since 1.0.3
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DelayerUsageTests {
-
+
@Autowired @Qualifier("inputA")
private MessageChannel inputA;
+
+ @Autowired
+ private MessageChannel delayerInsideChain;
+
@Autowired @Qualifier("outputA")
private PollableChannel outputA;
-
+
@Autowired @Qualifier("inputB")
private MessageChannel inputB;
+
@Autowired @Qualifier("outputB1")
private PollableChannel outputB1;
-
+
@Test
public void testDelayWithDefaultScheduler(){
long start = System.currentTimeMillis();
inputA.send(new GenericMessage("Hello"));
- outputA.receive();
+ assertNotNull(outputA.receive(10000));
assertTrue((System.currentTimeMillis() - start) >= 1000);
}
+
@Test
public void testDelayWithDefaultSchedulerCustomDelayHeader(){
MessageBuilder builder = MessageBuilder.withPayload("Hello");
// set custom delay header
builder.setHeader("foo", 2000);
long start = System.currentTimeMillis();
- inputA.send(builder.build());
- outputA.receive();
+ inputA.send(builder.build());
+ assertNotNull(outputA.receive(10000));
assertTrue((System.currentTimeMillis() - start) >= 2000);
}
+
@Test
- public void testDelayWithCustomScheduler(){
+ public void testDelayWithCustomScheduler(){
long start = System.currentTimeMillis();
- inputB.send(new GenericMessage("1"));
+ inputB.send(new GenericMessage("1"));
inputB.send(new GenericMessage("2"));
inputB.send(new GenericMessage("3"));
inputB.send(new GenericMessage("4"));
inputB.send(new GenericMessage("5"));
inputB.send(new GenericMessage("6"));
inputB.send(new GenericMessage("7"));
- outputB1.receive();
- outputB1.receive();
- outputB1.receive();
- outputB1.receive();
- outputB1.receive();
- outputB1.receive();
- outputB1.receive();
-
+ assertNotNull(outputB1.receive(10000));
+ assertNotNull(outputB1.receive(10000));
+ assertNotNull(outputB1.receive(10000));
+ assertNotNull(outputB1.receive(10000));
+ assertNotNull(outputB1.receive(10000));
+ assertNotNull(outputB1.receive(10000));
+ assertNotNull(outputB1.receive(10000));
+
// must execute under 3 seconds, since threadPool is set too 5.
// first batch is 5 concurrent invocations on SA, then 2 more
// elapsed time for the whole execution should be a bit over 2 seconds depending on the hardware
assertTrue(((System.currentTimeMillis() - start) >= 1000) && ((System.currentTimeMillis() - start) < 3000));
}
-
-
+
+ @Test //INT-1132
+ public void testDelayerInsideChain(){
+ long start = System.currentTimeMillis();
+ delayerInsideChain.send(new GenericMessage("Hello"));
+ Message> message = outputA.receive(10000);
+ assertNotNull(message);
+ assertTrue((System.currentTimeMillis() - start) >= 1000);
+ assertEquals("hello", message.getPayload());
+ }
+
public static class SampleService{
public String processMessage(String message) throws Exception {
Thread.sleep(500);
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/DelayHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/DelayHandlerTests.java
index 3aba426bb2..a13078ca7e 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/handler/DelayHandlerTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/DelayHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2012 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.
@@ -19,221 +19,204 @@ package org.springframework.integration.handler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
-
-import org.springframework.beans.DirectFieldAccessor;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageDeliveryException;
-import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.channel.MessagePublishingErrorHandler;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.message.GenericMessage;
+import org.springframework.integration.store.MessageGroup;
+import org.springframework.integration.store.MessageGroupStore;
+import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
+import org.springframework.integration.test.util.TestUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @author Mark Fisher
+ * @author Artem Bilan
* @since 1.0.3
*/
public class DelayHandlerTests {
+ private static final String DELAYER_MESSAGE_GROUP_ID = "testDelayer.messageGroupId";
+
private final DirectChannel input = new DirectChannel();
private final DirectChannel output = new DirectChannel();
private final CountDownLatch latch = new CountDownLatch(1);
+ private ThreadPoolTaskScheduler taskScheduler;
+
+ private DelayHandler delayHandler;
+
+ private ResultHandler resultHandler = new ResultHandler();
@Before
- public void setChannelNames() {
+ public void setup() {
input.setBeanName("input");
output.setBeanName("output");
+ taskScheduler = new ThreadPoolTaskScheduler();
+ taskScheduler.afterPropertiesSet();
+ delayHandler = new DelayHandler(DELAYER_MESSAGE_GROUP_ID, taskScheduler);
+ delayHandler.setOutputChannel(output);
+ input.subscribe(delayHandler);
+ output.subscribe(resultHandler);
}
+ private void startDelayerHandler() {
+ delayHandler.afterPropertiesSet();
+ delayHandler.onApplicationEvent(new ContextRefreshedEvent(TestUtils.createTestApplicationContext()));
+ }
@Test
public void noDelayHeaderAndDefaultDelayIsZero() throws Exception {
- DelayHandler delayHandler = new DelayHandler(0);
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Message> message = MessageBuilder.withPayload("test").build();
input.send(message);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void noDelayHeaderAndDefaultDelayIsPositive() throws Exception {
- DelayHandler delayHandler = new DelayHandler(10);
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ delayHandler.setDefaultDelay(10);
+ this.startDelayerHandler();
Message> message = MessageBuilder.withPayload("test").build();
input.send(message);
this.waitForLatch(1000);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertNotSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void delayHeaderAndDefaultDelayWouldTimeout() throws Exception {
- DelayHandler delayHandler = new DelayHandler(5000);
+ delayHandler.setDefaultDelay(5000);
delayHandler.setDelayHeaderName("delay");
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Message> message = MessageBuilder.withPayload("test")
.setHeader("delay", 100).build();
input.send(message);
this.waitForLatch(1000);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertNotSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void delayHeaderIsNegativeAndDefaultDelayWouldTimeout() throws Exception {
- DelayHandler delayHandler = new DelayHandler(5000);
+ delayHandler.setDefaultDelay(5000);
delayHandler.setDelayHeaderName("delay");
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Message> message = MessageBuilder.withPayload("test")
.setHeader("delay", -7000).build();
input.send(message);
this.waitForLatch(1000);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void delayHeaderIsInvalidFallsBackToDefaultDelay() throws Exception {
- DelayHandler delayHandler = new DelayHandler(5);
+ delayHandler.setDefaultDelay(5);
delayHandler.setDelayHeaderName("delay");
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Message> message = MessageBuilder.withPayload("test")
.setHeader("delay", "not a number").build();
input.send(message);
this.waitForLatch(1000);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertNotSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void delayHeaderIsDateInTheFutureAndDefaultDelayWouldTimeout() throws Exception {
- DelayHandler delayHandler = new DelayHandler(5000);
+ delayHandler.setDefaultDelay(5000);
delayHandler.setDelayHeaderName("delay");
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Message> message = MessageBuilder.withPayload("test")
.setHeader("delay", new Date(new Date().getTime() + 150)).build();
input.send(message);
this.waitForLatch(3000);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertNotSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void delayHeaderIsDateInThePastAndDefaultDelayWouldTimeout() throws Exception {
- DelayHandler delayHandler = new DelayHandler(5000);
+ delayHandler.setDefaultDelay(5000);
delayHandler.setDelayHeaderName("delay");
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Message> message = MessageBuilder.withPayload("test")
.setHeader("delay", new Date(new Date().getTime() - 60 * 1000)).build();
input.send(message);
this.waitForLatch(3000);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void delayHeaderIsNullDateAndDefaultDelayIsZero() throws Exception {
- DelayHandler delayHandler = new DelayHandler(0);
delayHandler.setDelayHeaderName("delay");
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Date nullDate = null;
Message> message = MessageBuilder.withPayload("test")
.setHeader("delay", nullDate).build();
input.send(message);
this.waitForLatch(3000);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test(expected = TestTimedOutException.class)
public void delayHeaderIsFutureDateAndTimesOut() throws Exception {
- DelayHandler delayHandler = new DelayHandler(0);
delayHandler.setDelayHeaderName("delay");
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Date future = new Date(new Date().getTime() + 60 * 1000);
Message> message = MessageBuilder.withPayload("test")
.setHeader("delay", future).build();
input.send(message);
this.waitForLatch(50);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void delayHeaderIsValidStringAndDefaultDelayWouldTimeout() throws Exception {
- DelayHandler delayHandler = new DelayHandler(5000);
+ delayHandler.setDefaultDelay(5000);
delayHandler.setDelayHeaderName("delay");
- ResultHandler resultHandler = new ResultHandler();
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
- output.subscribe(resultHandler);
+ this.startDelayerHandler();
Message> message = MessageBuilder.withPayload("test")
.setHeader("delay", "20").build();
input.send(message);
this.waitForLatch(1000);
- assertSame(message, resultHandler.lastMessage);
+ assertSame(message.getPayload(), resultHandler.lastMessage.getPayload());
assertNotSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void verifyShutdownWithoutWaitingByDefault() throws Exception {
- DelayHandler delayHandler = new DelayHandler(5000);
- delayHandler.afterPropertiesSet();
+ delayHandler.setDefaultDelay(5000);
+ this.startDelayerHandler();
delayHandler.handleMessage(new GenericMessage("foo"));
- delayHandler.destroy();
- final ThreadPoolTaskScheduler taskScheduler = (ThreadPoolTaskScheduler)
- new DirectFieldAccessor(delayHandler).getPropertyValue("taskScheduler");
+ taskScheduler.destroy();
+
final CountDownLatch latch = new CountDownLatch(1);
new Thread(new Runnable() {
public void run() {
@@ -252,13 +235,12 @@ public class DelayHandlerTests {
@Test
public void verifyShutdownWithWait() throws Exception {
- DelayHandler delayHandler = new DelayHandler(5000);
- delayHandler.setWaitForTasksToCompleteOnShutdown(true);
- delayHandler.afterPropertiesSet();
+ delayHandler.setDefaultDelay(5000);
+ taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
+ this.startDelayerHandler();
delayHandler.handleMessage(new GenericMessage("foo"));
- delayHandler.destroy();
- final ThreadPoolTaskScheduler taskScheduler = (ThreadPoolTaskScheduler)
- new DirectFieldAccessor(delayHandler).getPropertyValue("taskScheduler");
+ taskScheduler.destroy();
+
final CountDownLatch latch = new CountDownLatch(1);
new Thread(new Runnable() {
public void run() {
@@ -277,10 +259,8 @@ public class DelayHandlerTests {
@Test(expected = MessageDeliveryException.class)
public void handlerThrowsExceptionWithNoDelay() throws Exception {
- DelayHandler delayHandler = new DelayHandler(0);
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- input.subscribe(delayHandler);
+ this.startDelayerHandler();
+ output.unsubscribe(resultHandler);
output.subscribe(new MessageHandler() {
public void handleMessage(Message> message) {
throw new UnsupportedOperationException("intentional test failure");
@@ -292,14 +272,14 @@ public class DelayHandlerTests {
@Test
public void errorChannelHeaderAndHandlerThrowsExceptionWithDelay() throws Exception {
- DelayHandler delayHandler = new DelayHandler(0);
- delayHandler.setDelayHeaderName("delay");
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
DirectChannel errorChannel = new DirectChannel();
- ResultHandler resultHandler = new ResultHandler();
+ MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler();
+ errorHandler.setDefaultErrorChannel(errorChannel);
+ taskScheduler.setErrorHandler(errorHandler);
+ delayHandler.setDelayHeaderName("delay");
+ this.startDelayerHandler();
+ output.unsubscribe(resultHandler);
errorChannel.subscribe(resultHandler);
- input.subscribe(delayHandler);
output.subscribe(new MessageHandler() {
public void handleMessage(Message> message) {
throw new UnsupportedOperationException("intentional test failure");
@@ -311,13 +291,10 @@ public class DelayHandlerTests {
input.send(message);
this.waitForLatch(1000);
Message> errorMessage = resultHandler.lastMessage;
- assertEquals(MessageHandlingException.class, errorMessage.getPayload().getClass());
- MessageHandlingException exceptionPayload = (MessageHandlingException) errorMessage.getPayload();
- assertSame(message, exceptionPayload.getFailedMessage());
- assertEquals(MessageDeliveryException.class, exceptionPayload.getCause().getClass());
- MessageDeliveryException nestedException = (MessageDeliveryException) exceptionPayload.getCause();
- assertEquals(UnsupportedOperationException.class, nestedException.getCause().getClass());
- assertSame(message, nestedException.getFailedMessage());
+ assertEquals(MessageDeliveryException.class, errorMessage.getPayload().getClass());
+ MessageDeliveryException exceptionPayload = (MessageDeliveryException) errorMessage.getPayload();
+ assertSame(message.getPayload(), exceptionPayload.getFailedMessage().getPayload());
+ assertEquals(UnsupportedOperationException.class, exceptionPayload.getCause().getClass());
assertNotSame(Thread.currentThread(), resultHandler.lastThread);
}
@@ -326,16 +303,16 @@ public class DelayHandlerTests {
String errorChannelName = "customErrorChannel";
StaticApplicationContext context = new StaticApplicationContext();
context.registerSingleton(errorChannelName, DirectChannel.class);
+ context.registerSingleton(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME, DirectChannel.class);
context.refresh();
DirectChannel customErrorChannel = (DirectChannel) context.getBean(errorChannelName);
- DelayHandler delayHandler = new DelayHandler(0);
- delayHandler.setBeanFactory(context);
+ MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler();
+ errorHandler.setBeanFactory(context);
+ taskScheduler.setErrorHandler(errorHandler);
delayHandler.setDelayHeaderName("delay");
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- ResultHandler resultHandler = new ResultHandler();
+ this.startDelayerHandler();
+ output.unsubscribe(resultHandler);
customErrorChannel.subscribe(resultHandler);
- input.subscribe(delayHandler);
output.subscribe(new MessageHandler() {
public void handleMessage(Message> message) {
throw new UnsupportedOperationException("intentional test failure");
@@ -347,32 +324,26 @@ public class DelayHandlerTests {
input.send(message);
this.waitForLatch(1000);
Message> errorMessage = resultHandler.lastMessage;
- assertEquals(MessageHandlingException.class, errorMessage.getPayload().getClass());
- MessageHandlingException exceptionPayload = (MessageHandlingException) errorMessage.getPayload();
- assertSame(message, exceptionPayload.getFailedMessage());
- assertEquals(MessageDeliveryException.class, exceptionPayload.getCause().getClass());
- MessageDeliveryException nestedException = (MessageDeliveryException) exceptionPayload.getCause();
- assertEquals(UnsupportedOperationException.class, nestedException.getCause().getClass());
- assertSame(message, nestedException.getFailedMessage());
+ assertEquals(MessageDeliveryException.class, errorMessage.getPayload().getClass());
+ MessageDeliveryException exceptionPayload = (MessageDeliveryException) errorMessage.getPayload();
+ assertSame(message.getPayload(), exceptionPayload.getFailedMessage().getPayload());
+ assertEquals(UnsupportedOperationException.class, exceptionPayload.getCause().getClass());
assertNotSame(Thread.currentThread(), resultHandler.lastThread);
}
@Test
public void defaultErrorChannelAndHandlerThrowsExceptionWithDelay() throws Exception {
StaticApplicationContext context = new StaticApplicationContext();
- context.registerSingleton(
- IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME, DirectChannel.class);
+ context.registerSingleton(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME, DirectChannel.class);
context.refresh();
- DirectChannel defaultErrorChannel = (DirectChannel) context.getBean(
- IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME);
- DelayHandler delayHandler = new DelayHandler(0);
- delayHandler.setBeanFactory(context);
+ DirectChannel defaultErrorChannel = (DirectChannel) context.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME);
+ MessagePublishingErrorHandler errorHandler = new MessagePublishingErrorHandler();
+ errorHandler.setBeanFactory(context);
+ taskScheduler.setErrorHandler(errorHandler);
delayHandler.setDelayHeaderName("delay");
- delayHandler.setOutputChannel(output);
- delayHandler.afterPropertiesSet();
- ResultHandler resultHandler = new ResultHandler();
+ this.startDelayerHandler();
+ output.unsubscribe(resultHandler);
defaultErrorChannel.subscribe(resultHandler);
- input.subscribe(delayHandler);
output.subscribe(new MessageHandler() {
public void handleMessage(Message> message) {
throw new UnsupportedOperationException("intentional test failure");
@@ -383,17 +354,72 @@ public class DelayHandlerTests {
input.send(message);
this.waitForLatch(1000);
Message> errorMessage = resultHandler.lastMessage;
- assertEquals(MessageHandlingException.class, errorMessage.getPayload().getClass());
- MessageHandlingException exceptionPayload = (MessageHandlingException) errorMessage.getPayload();
- assertSame(message, exceptionPayload.getFailedMessage());
- assertEquals(MessageDeliveryException.class, exceptionPayload.getCause().getClass());
- MessageDeliveryException nestedException = (MessageDeliveryException) exceptionPayload.getCause();
- assertEquals(UnsupportedOperationException.class, nestedException.getCause().getClass());
- assertSame(message, nestedException.getFailedMessage());
+ assertEquals(MessageDeliveryException.class, errorMessage.getPayload().getClass());
+ MessageDeliveryException exceptionPayload = (MessageDeliveryException) errorMessage.getPayload();
+ assertSame(message.getPayload(), exceptionPayload.getFailedMessage().getPayload());
+ assertEquals(UnsupportedOperationException.class, exceptionPayload.getCause().getClass());
assertNotSame(Thread.currentThread(), resultHandler.lastThread);
}
+ @Test //INT-1132
+ public void testReschedulePersistedMessagesOnStartup() throws Exception {
+ MessageGroupStore messageGroupStore = new SimpleMessageStore();
+ this.delayHandler.setDefaultDelay(200);
+ this.delayHandler.setMessageStore(messageGroupStore);
+ this.startDelayerHandler();
+ Message> message = MessageBuilder.withPayload("test").build();
+ this.input.send(message);
+
+ Thread.sleep(100);
+
+ // emulate restart
+ this.taskScheduler.destroy();
+
+ assertEquals(1, messageGroupStore.getMessageGroupCount());
+ assertEquals(DELAYER_MESSAGE_GROUP_ID, messageGroupStore.iterator().next().getGroupId());
+ assertEquals(1, messageGroupStore.messageGroupSize(DELAYER_MESSAGE_GROUP_ID));
+ assertEquals(1, messageGroupStore.getMessageCountForAllMessageGroups());
+ MessageGroup messageGroup = messageGroupStore.getMessageGroup(DELAYER_MESSAGE_GROUP_ID);
+ Message> messageInStore = messageGroup.getMessages().iterator().next();
+ Object payload = messageInStore.getPayload();
+ assertEquals("DelayedMessageWrapper", payload.getClass().getSimpleName());
+ assertEquals(message.getPayload(), TestUtils.getPropertyValue(payload, "original.payload"));
+
+ this.taskScheduler.afterPropertiesSet();
+ this.delayHandler = new DelayHandler(DELAYER_MESSAGE_GROUP_ID, this.taskScheduler);
+ this.delayHandler.setOutputChannel(output);
+ this.delayHandler.setDefaultDelay(200);
+ this.delayHandler.setMessageStore(messageGroupStore);
+ this.startDelayerHandler();
+
+ long timeBeforeReceive = System.currentTimeMillis();
+ assertTrue(this.latch.await(10, TimeUnit.SECONDS));
+ long timeAfterReceive = System.currentTimeMillis();
+ assertThat(timeAfterReceive - timeBeforeReceive, Matchers.lessThanOrEqualTo(100L));
+
+ assertSame(message.getPayload(), this.resultHandler.lastMessage.getPayload());
+ assertNotSame(Thread.currentThread(), this.resultHandler.lastThread);
+ assertEquals(1, messageGroupStore.getMessageGroupCount());
+ assertEquals(0, messageGroupStore.messageGroupSize(DELAYER_MESSAGE_GROUP_ID));
+ }
+
+ @Test //INT-1132
+ // Can happen in the parent-child context e.g. Spring-MVC applications
+ public void testDoubleOnApplicationEvent() throws Exception {
+ this.delayHandler = Mockito.spy(this.delayHandler);
+ Mockito.doAnswer(new Answer() {
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return null;
+ }
+ }).when(this.delayHandler).reschedulePersistedMessages();
+
+ ContextRefreshedEvent contextRefreshedEvent = new ContextRefreshedEvent(TestUtils.createTestApplicationContext());
+ this.delayHandler.onApplicationEvent(contextRefreshedEvent);
+ this.delayHandler.onApplicationEvent(contextRefreshedEvent);
+ Mockito.verify(this.delayHandler, Mockito.times(1)).reschedulePersistedMessages();
+ }
+
private void waitForLatch(long timeout) {
try {
this.latch.await(timeout, TimeUnit.MILLISECONDS);
diff --git a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.2.xsd b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.2.xsd
index eb0efe1698..627fee4e21 100644
--- a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.2.xsd
+++ b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.2.xsd
@@ -7,7 +7,7 @@
elementFormDefault="qualified" attributeFormDefault="unqualified">
@@ -53,7 +53,7 @@
-
+
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/DelayerHandlerRescheduleIntegrationTests-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/DelayerHandlerRescheduleIntegrationTests-context.xml
new file mode 100644
index 0000000000..78b3f2f9ed
--- /dev/null
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/DelayerHandlerRescheduleIntegrationTests-context.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/DelayerHandlerRescheduleIntegrationTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/DelayerHandlerRescheduleIntegrationTests.java
new file mode 100644
index 0000000000..702d500908
--- /dev/null
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/DelayerHandlerRescheduleIntegrationTests.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2002-2012 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.jdbc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+import org.hamcrest.Matchers;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.core.serializer.support.DeserializingConverter;
+import org.springframework.integration.Message;
+import org.springframework.integration.MessageChannel;
+import org.springframework.integration.core.PollableChannel;
+import org.springframework.integration.store.MessageGroup;
+import org.springframework.integration.store.MessageGroupStore;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.integration.test.util.TestUtils;
+import org.springframework.integration.util.UUIDConverter;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+import org.springframework.jdbc.support.lob.DefaultLobHandler;
+import org.springframework.jdbc.support.lob.LobHandler;
+
+/**
+ * @author Arte Bilan
+ */
+//INT-1132
+public class DelayerHandlerRescheduleIntegrationTests {
+
+ public static final String DELAYER_ID = "delayerWithJdbcMS";
+
+ private static EmbeddedDatabase dataSource;
+
+ @BeforeClass
+ public static void init() {
+ dataSource = new EmbeddedDatabaseBuilder()
+ .setType(EmbeddedDatabaseType.DERBY)
+ .addScript("classpath:/org/springframework/integration/jdbc/schema-derby.sql")
+ .build();
+ }
+
+ @AfterClass
+ public static void destroy() {
+ dataSource.shutdown();
+ }
+
+ @Test
+ public void testDelayerHandlerRescheduleWithJdbcMessageStore() throws Exception {
+ AbstractApplicationContext context = new ClassPathXmlApplicationContext("DelayerHandlerRescheduleIntegrationTests-context.xml", this.getClass());
+ MessageChannel input = context.getBean("input", MessageChannel.class);
+ MessageGroupStore messageStore = context.getBean("messageStore", MessageGroupStore.class);
+
+ assertEquals(0, messageStore.getMessageGroupCount());
+ input.send(MessageBuilder.withPayload("test1").build());
+ input.send(MessageBuilder.withPayload("test2").build());
+
+ // Emulate restart and check DB state before next start
+ context.destroy();
+
+ Thread.sleep(100);
+
+ try {
+ context.getBean("input", MessageChannel.class);
+ fail("IllegalStateException expected");
+ }
+ catch (Exception e) {
+ assertTrue(e instanceof IllegalStateException);
+ assertTrue(e.getMessage().contains("BeanFactory not initialized or already closed - call 'refresh'"));
+ }
+
+ String delayerMessageGroupId = UUIDConverter.getUUID(DELAYER_ID + ".messageGroupId").toString();
+
+ assertEquals(1, messageStore.getMessageGroupCount());
+ assertEquals(delayerMessageGroupId, messageStore.iterator().next().getGroupId());
+ assertEquals(2, messageStore.messageGroupSize(delayerMessageGroupId));
+ assertEquals(2, messageStore.getMessageCountForAllMessageGroups());
+ MessageGroup messageGroup = messageStore.getMessageGroup(delayerMessageGroupId);
+ Message> messageInStore = messageGroup.getMessages().iterator().next();
+ Object payload = messageInStore.getPayload();
+ assertEquals("DelayedMessageWrapper", payload.getClass().getSimpleName());
+ assertEquals("test1", TestUtils.getPropertyValue(payload, "original.payload"));
+
+ context.refresh();
+
+ PollableChannel output = context.getBean("output", PollableChannel.class);
+
+ long timeBeforeReceive = System.currentTimeMillis();
+ Message> message = output.receive(10000);
+ assertNotNull(message);
+ long timeAfterReceive = System.currentTimeMillis();
+ assertThat(timeAfterReceive - timeBeforeReceive, Matchers.lessThanOrEqualTo(100L));
+
+ Object payload1 = message.getPayload();
+
+ message = output.receive(10000);
+ assertNotNull(message);
+ Object payload2 = message.getPayload();
+ assertNotSame(payload1, payload2);
+
+ assertEquals(1, messageStore.getMessageGroupCount());
+ assertEquals(0, messageStore.messageGroupSize(delayerMessageGroupId));
+
+ }
+
+ private static class TestJdbcMessageStore extends JdbcMessageStore {
+
+ private volatile LobHandler lobHandler = new DefaultLobHandler();
+
+ private volatile MessageMapper mapper = new MessageMapper();
+
+ private volatile DeserializingConverter deserializer = new DeserializingConverter();
+
+ private TestJdbcMessageStore() {
+ super();
+ this.setDataSource(dataSource);
+ }
+
+
+// LIFO polling
+ @Override
+ protected Message> doPollForMessage(String groupIdKey) {
+ List> messages = this.getJdbcOperations()
+ .query("SELECT INT_MESSAGE.MESSAGE_ID, INT_MESSAGE.MESSAGE_BYTES from INT_MESSAGE " +
+ "where INT_MESSAGE.MESSAGE_ID = " +
+ "(SELECT max(MESSAGE_ID) from INT_MESSAGE where CREATED_DATE = " +
+ "(SELECT max(CREATED_DATE) from INT_MESSAGE, INT_GROUP_TO_MESSAGE " +
+ "where INT_MESSAGE.MESSAGE_ID = INT_GROUP_TO_MESSAGE.MESSAGE_ID " +
+ "and INT_GROUP_TO_MESSAGE.GROUP_KEY = ?))", new Object[]{groupIdKey}, mapper);
+ if (messages.size() > 0) {
+ System.out.println(messages.get(0));
+ return messages.get(0);
+ }
+ return null;
+ }
+
+ private class MessageMapper implements RowMapper> {
+
+ public Message> mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return (Message>) deserializer.convert(lobHandler.getBlobAsBytes(rs, "MESSAGE_BYTES"));
+ }
+ }
+
+ }
+
+}