diff --git a/.gitignore b/.gitignore
index 8eedbd10e5..a085e36f76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
lib
+logs
target
.springBeans
.settings
diff --git a/spring-integration-core/pom.xml b/spring-integration-core/pom.xml
index 62ae5a8e8f..f42fe281a7 100644
--- a/spring-integration-core/pom.xml
+++ b/spring-integration-core/pom.xml
@@ -30,11 +30,6 @@
spring-txtrue
-
- org.springframework.commons
- spring-commons-serializer
- true
- junit
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageHandler.java
index b8c200b889..75b56aa947 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageHandler.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/CorrelatingMessageHandler.java
@@ -142,6 +142,13 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
this.sendPartialResultOnExpiry = sendPartialResultOnExpiry;
}
+ public void setReleasePartialSequences(boolean releasePartialSequences){
+ Assert.isInstanceOf(SequenceSizeReleaseStrategy.class, this.releaseStrategy,
+ "Release strategy of type [" + this.releaseStrategy.getClass().getSimpleName()
+ + "] cannot release partial sequences. Use the default SequenceSizeReleaseStrategy instead.");
+ ((SequenceSizeReleaseStrategy)this.releaseStrategy).setReleasePartialSequences(releasePartialSequences);
+ }
+
@Override
public String getComponentType() {
return "aggregator";
@@ -162,6 +169,9 @@ public class CorrelatingMessageHandler extends AbstractMessageHandler implements
synchronized (lock) {
MessageGroup group = messageStore.getMessageGroup(correlationKey);
if (group.canAdd(message)) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Adding message to group [ " + group + "]");
+ }
group = store(correlationKey, message);
if (releaseStrategy.canRelease(group)) {
Collection completedMessages = null;
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java
index b98eeaa71d..bc4075fc76 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/SequenceSizeReleaseStrategy.java
@@ -16,6 +16,8 @@
package org.springframework.integration.aggregator;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.integration.Message;
import org.springframework.integration.store.MessageGroup;
@@ -31,9 +33,12 @@ import java.util.List;
* @author Mark Fisher
* @author Marius Bogoevici
* @author Dave Syer
+ * @author Iwein Fuld
*/
public class SequenceSizeReleaseStrategy implements ReleaseStrategy {
+ private static final Log logger = LogFactory.getLog(SequenceSizeReleaseStrategy.class);
+
private volatile Comparator> comparator = new SequenceNumberComparator();
private volatile boolean releasePartialSequences;
@@ -58,10 +63,17 @@ public class SequenceSizeReleaseStrategy implements ReleaseStrategy {
public boolean canRelease(MessageGroup messages) {
if (releasePartialSequences) {
+ if(logger.isTraceEnabled()){
+ logger.trace("Considering partial release of group [" + messages + "]");
+ }
List> sorted = new ArrayList>(messages.getUnmarked());
Collections.sort(sorted, comparator);
int tail = sorted.get(0).getHeaders().getSequenceNumber() - 1;
- return tail == messages.getMarked().size();
+ boolean release = tail == messages.getMarked().size();
+ if (logger.isTraceEnabled() && release) {
+ logger.trace("Release imminent because tail [" + tail + "] is next in line.");
+ }
+ return release;
}
return messages.isComplete();
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java
index d3ebf8132b..b046bc13ee 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java
@@ -16,6 +16,8 @@
package org.springframework.integration.config;
+import org.springframework.aop.TargetSource;
+import org.springframework.aop.framework.Advised;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@@ -27,9 +29,9 @@ import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.MessageChannel;
+import org.springframework.integration.context.Orderable;
import org.springframework.integration.core.MessageHandler;
-import org.springframework.integration.handler.AbstractMessageHandler;
-import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
+import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -100,14 +102,14 @@ abstract class AbstractMessageHandlerFactoryBean implements FactoryBean T extractTypeIfPossible(Object targetObject, Class expectedType) {
+ if (targetObject == null) {
+ return null;
+ }
+ if (expectedType.isAssignableFrom(targetObject.getClass())) {
+ return (T) targetObject;
+ }
+ if (targetObject instanceof Advised) {
+ TargetSource targetSource = ((Advised) targetObject).getTargetSource();
+ if (targetSource == null) {
+ return null;
+ }
+ try {
+ return extractTypeIfPossible(targetSource.getTarget(), expectedType);
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ return null;
+ }
+
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java
index 4d804179e8..ba95bf6eb4 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java
@@ -15,8 +15,6 @@ package org.springframework.integration.config;
import java.util.Map;
-import org.springframework.aop.TargetSource;
-import org.springframework.aop.framework.Advised;
import org.springframework.expression.Expression;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.core.MessageHandler;
@@ -33,6 +31,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author Jonas Partner
* @author Oleg Zhurakousky
+ * @author Dave Syer
*/
public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean {
@@ -52,6 +51,7 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean {
private volatile Boolean ignoreSendFailures;
+
public void setChannelResolver(ChannelResolver channelResolver) {
this.channelResolver = channelResolver;
}
@@ -86,49 +86,21 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean {
@Override
MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) {
-
Assert.notNull(targetObject, "target object must not be null");
- AbstractMessageRouter router = extractRouter(targetObject);
-
+ AbstractMessageRouter router = this.extractTypeIfPossible(targetObject, AbstractMessageRouter.class);
if (router == null) {
- router = this.createRouter(targetObject, targetMethodName);
+ router = this.createMethodInvokingRouter(targetObject, targetMethodName);
this.configureRouter(router);
- return router;
}
-
- Assert.isTrue(!StringUtils.hasText(targetMethodName), "target method should not be provided when the target "
- + "object is an implementation of AbstractMessageRouter");
- this.configureRouter(router);
-
- if (targetObject instanceof MessageHandler) {
- return (MessageHandler) targetObject;
+ else {
+ Assert.isTrue(!StringUtils.hasText(targetMethodName), "target method should not be provided when the target "
+ + "object is an implementation of AbstractMessageRouter");
+ this.configureRouter(router);
+ if (targetObject instanceof MessageHandler) {
+ return (MessageHandler) targetObject;
+ }
}
return router;
-
- }
-
- private AbstractMessageRouter extractRouter(Object targetObject) {
- if (targetObject instanceof AbstractMessageRouter) {
- return (AbstractMessageRouter) targetObject;
- }
- if (targetObject instanceof Advised) {
- return extractAopTarget((Advised) targetObject);
- }
- return null;
- }
-
- private AbstractMessageRouter extractAopTarget(Advised advised) {
- TargetSource targetSource = advised.getTargetSource();
- if (targetSource == null) {
- return null;
- }
- Object target;
- try {
- target = targetSource.getTarget();
- } catch (Exception e) {
- throw new IllegalStateException(e);
- }
- return extractRouter(target);
}
@Override
@@ -136,18 +108,19 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean {
return this.configureRouter(new ExpressionEvaluatingRouter(expression));
}
- private AbstractMessageRouter createRouter(Object targetObject, String targetMethodName) {
- MethodInvokingRouter router = (StringUtils.hasText(targetMethodName)) ? new MethodInvokingRouter(targetObject,
- targetMethodName) : new MethodInvokingRouter(targetObject);
+ private AbstractMessageRouter createMethodInvokingRouter(Object targetObject, String targetMethodName) {
+ MethodInvokingRouter router = (StringUtils.hasText(targetMethodName))
+ ? new MethodInvokingRouter(targetObject, targetMethodName)
+ : new MethodInvokingRouter(targetObject);
return router;
}
private AbstractMessageRouter configureRouter(AbstractMessageRouter router) {
- if (this.channelResolver != null && router instanceof AbstractMessageRouter) {
- ((AbstractMessageRouter) router).setChannelResolver(this.channelResolver);
+ if (this.channelResolver != null) {
+ router.setChannelResolver(this.channelResolver);
}
- if (this.channelIdentifierMap != null && router instanceof AbstractMessageRouter) {
- ((AbstractMessageRouter) router).setChannelIdentifierMap(this.channelIdentifierMap);
+ if (this.channelIdentifierMap != null) {
+ router.setChannelIdentifierMap(this.channelIdentifierMap);
}
if (this.defaultOutputChannel != null) {
router.setDefaultOutputChannel(this.defaultOutputChannel);
@@ -156,11 +129,7 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean {
router.setTimeout(timeout.longValue());
}
if (this.ignoreChannelNameResolutionFailures != null) {
- Assert.isTrue(router instanceof AbstractMessageRouter,
- "The 'ignoreChannelNameResolutionFailures' property can only be set on routers that extend "
- + AbstractMessageRouter.class.getName());
- ((AbstractMessageRouter) router)
- .setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures);
+ router.setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures);
}
if (this.applySequence != null) {
router.setApplySequence(this.applySequence);
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java
index 31dc216dcb..92a4736230 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java
@@ -22,12 +22,14 @@ import org.springframework.integration.splitter.AbstractMessageSplitter;
import org.springframework.integration.splitter.DefaultMessageSplitter;
import org.springframework.integration.splitter.ExpressionEvaluatingSplitter;
import org.springframework.integration.splitter.MethodInvokingSplitter;
+import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Factory bean for creating a Message Splitter.
*
* @author Mark Fisher
+ * @author Iwein Fuld
*/
public class SplitterFactoryBean extends AbstractMessageHandlerFactoryBean {
@@ -35,6 +37,8 @@ public class SplitterFactoryBean extends AbstractMessageHandlerFactoryBean {
private volatile boolean requiresReply;
+ private volatile boolean applySequence = true;
+
public void setSendTimeout(Long sendTimeout) {
this.sendTimeout = sendTimeout;
@@ -48,18 +52,33 @@ public class SplitterFactoryBean extends AbstractMessageHandlerFactoryBean {
this.requiresReply = requiresReply;
}
+ public void setApplySequence(boolean applySequence) {
+ this.applySequence = applySequence;
+ }
+
@Override
MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) {
- AbstractMessageSplitter splitter = null;
- if (targetObject instanceof AbstractMessageSplitter) {
- splitter = (AbstractMessageSplitter) targetObject;
+ Assert.notNull(targetObject, "targetObject must not be null");
+ AbstractMessageSplitter splitter = this.extractTypeIfPossible(targetObject, AbstractMessageSplitter.class);
+ if (splitter == null) {
+ splitter = this.createMethodInvokingSplitter(targetObject, targetMethodName);
+ this.configureSplitter(splitter);
}
else {
- splitter = (StringUtils.hasText(targetMethodName))
- ? new MethodInvokingSplitter(targetObject, targetMethodName)
- : new MethodInvokingSplitter(targetObject);
+ Assert.isTrue(!StringUtils.hasText(targetMethodName), "target method should not be provided when the target "
+ + "object is an implementation of AbstractMessageSplitter");
+ this.configureSplitter(splitter);
+ if (targetObject instanceof MessageHandler) {
+ return (MessageHandler) targetObject;
+ }
}
- return this.configureSplitter(splitter);
+ return splitter;
+ }
+
+ private AbstractMessageSplitter createMethodInvokingSplitter(Object targetObject, String targetMethodName) {
+ return (StringUtils.hasText(targetMethodName))
+ ? new MethodInvokingSplitter(targetObject, targetMethodName)
+ : new MethodInvokingSplitter(targetObject);
}
@Override
@@ -77,6 +96,7 @@ public class SplitterFactoryBean extends AbstractMessageHandlerFactoryBean {
splitter.setSendTimeout(sendTimeout);
}
splitter.setRequiresReply(requiresReply);
+ splitter.setApplySequence(applySequence);
return splitter;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java
index 4907528a31..140d60cc09 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/annotation/AbstractMethodAnnotationPostProcessor.java
@@ -26,11 +26,11 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.integration.MessageChannel;
+import org.springframework.integration.context.Orderable;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
-import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.integration.support.channel.ChannelResolver;
import org.springframework.util.Assert;
@@ -60,10 +60,10 @@ public abstract class AbstractMethodAnnotationPostProcessor
- * The property name will be the camel-case equivalent of the lower case
- * hyphen separated attribute (e.g. the "foo-bar" attribute would match the
- * "fooBar" property).
+ * The property name will be the camel-case equivalent of the lower case hyphen separated attribute (e.g. the
+ * "foo-bar" attribute would match the "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
- * @param builder
- * the bean definition builder to be configured
- * @param element
- * - the XML element where the attribute should be defined
- * @param attributeName
- * - the name of the attribute whose value will be set on the
- * property
+ * @param builder the bean definition builder to be configured
+ * @param element - the XML element where the attribute should be defined
+ * @param attributeName - the name of the attribute whose value will be set on the property
*/
- public static void setValueIfAttributeDefined(
- BeanDefinitionBuilder builder, Element element, String attributeName) {
- setValueIfAttributeDefined(builder, element, attributeName, Conventions
- .attributeNameToPropertyName(attributeName));
+ public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element, String attributeName) {
+ setValueIfAttributeDefined(builder, element, attributeName,
+ Conventions.attributeNameToPropertyName(attributeName));
}
/**
- * Configures the provided bean definition builder with a property reference
- * to a bean. The bean reference is identified by the value from the
- * attribute whose name is provided if that attribute is defined in the
- * given element.
+ * Configures the provided bean definition builder with a property reference to a bean. The bean reference is
+ * identified by the value from the attribute whose name is provided if that attribute is defined in the given
+ * element.
*
- * @param builder
- * the bean definition builder to be configured
- * @param element
- * the XML element where the attribute should be defined
- * @param attributeName
- * the name of the attribute whose value will be used as a bean
- * reference to populate the property
- * @param propertyName
- * the name of the property to be populated
+ * @param builder the bean definition builder to be configured
+ * @param element the XML element where the attribute should be defined
+ * @param attributeName the name of the attribute whose value will be used as a bean reference to populate the
+ * property
+ * @param propertyName the name of the property to be populated
*/
- public static void setReferenceIfAttributeDefined(
- BeanDefinitionBuilder builder, Element element,
+ public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, String propertyName) {
String attributeValue = element.getAttribute(attributeName);
if (StringUtils.hasText(attributeValue)) {
@@ -122,38 +100,32 @@ public abstract class IntegrationNamespaceUtils {
}
/**
- * Configures the provided bean definition builder with a property reference
- * to a bean. The bean reference is identified by the value from the
- * attribute whose name is provided if that attribute is defined in the
- * given element.
+ * Configures the provided bean definition builder with a property reference to a bean. The bean reference is
+ * identified by the value from the attribute whose name is provided if that attribute is defined in the given
+ * element.
*
*
- * The property name will be the camel-case equivalent of the lower case
- * hyphen separated attribute (e.g. the "foo-bar" attribute would match the
- * "fooBar" property).
+ * The property name will be the camel-case equivalent of the lower case hyphen separated attribute (e.g. the
+ * "foo-bar" attribute would match the "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
- * @param builder
- * the bean definition builder to be configured
- * @param element
- * - the XML element where the attribute should be defined
- * @param attributeName
- * - the name of the attribute whose value will be used as a bean
- * reference to populate the property
+ * @param builder the bean definition builder to be configured
+ * @param element - the XML element where the attribute should be defined
+ * @param attributeName - the name of the attribute whose value will be used as a bean reference to populate the
+ * property
*
* @see Conventions#attributeNameToPropertyName(String)
*/
- public static void setReferenceIfAttributeDefined(
- BeanDefinitionBuilder builder, Element element, String attributeName) {
+ public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
+ String attributeName) {
setReferenceIfAttributeDefined(builder, element, attributeName,
Conventions.attributeNameToPropertyName(attributeName));
}
/**
- * Provides a user friendly description of an element based on its node name
- * and, if available, its "id" attribute value. This is useful for creating
- * error messages from within bean definition parsers.
+ * Provides a user friendly description of an element based on its node name and, if available, its "id" attribute
+ * value. This is useful for creating error messages from within bean definition parsers.
*/
public static String createElementDescription(Element element) {
String elementId = "'" + element.getNodeName() + "'";
@@ -165,70 +137,51 @@ public abstract class IntegrationNamespaceUtils {
}
/**
- * Parse a "poller" element to provide a reference for the target
- * BeanDefinitionBuilder. If the poller element does not contain a "ref"
- * attribute, this will create and register a PollerMetadata instance and
- * then add it as a property reference of the target builder.
+ * Parse a "poller" element to provide a reference for the target BeanDefinitionBuilder. If the poller element does
+ * not contain a "ref" attribute, this will create and register a PollerMetadata instance and then add it as a
+ * property reference of the target builder.
*
- * @param pollerElement
- * the "poller" element to parse
- * @param targetBuilder
- * the builder that expects the "trigger" property
- * @param parserContext
- * the parserContext for the target builder
+ * @param pollerElement the "poller" element to parse
+ * @param targetBuilder the builder that expects the "trigger" property
+ * @param parserContext the parserContext for the target builder
*/
- public static void configurePollerMetadata(Element pollerElement,
- BeanDefinitionBuilder targetBuilder, ParserContext parserContext) {
+ public static void configurePollerMetadata(Element pollerElement, BeanDefinitionBuilder targetBuilder,
+ ParserContext parserContext) {
if (pollerElement.hasAttribute("ref")) {
if (pollerElement.getAttributes().getLength() != 1) {
- parserContext
- .getReaderContext()
- .error(
- "A 'poller' element that provides a 'ref' must have no other attributes.",
- pollerElement);
+ parserContext.getReaderContext().error(
+ "A 'poller' element that provides a 'ref' must have no other attributes.", pollerElement);
}
if (pollerElement.getChildNodes().getLength() != 0) {
- parserContext
- .getReaderContext()
- .error(
- "A 'poller' element that provides a 'ref' must have no child elements.",
- pollerElement);
- }
- targetBuilder.addPropertyReference("pollerMetadata", pollerElement
- .getAttribute("ref"));
- } else {
- BeanDefinition beanDefinition = parserContext.getDelegate()
- .parseCustomElement(pollerElement,
- targetBuilder.getBeanDefinition());
- if (beanDefinition == null) {
parserContext.getReaderContext().error(
- "BeanDefinition must not be null", pollerElement);
+ "A 'poller' element that provides a 'ref' must have no child elements.", pollerElement);
+ }
+ targetBuilder.addPropertyReference("pollerMetadata", pollerElement.getAttribute("ref"));
+ } else {
+ BeanDefinition beanDefinition = parserContext.getDelegate().parseCustomElement(pollerElement,
+ targetBuilder.getBeanDefinition());
+ if (beanDefinition == null) {
+ parserContext.getReaderContext().error("BeanDefinition must not be null", pollerElement);
}
targetBuilder.addPropertyValue("pollerMetadata", beanDefinition);
}
}
/**
- * Get a text value from a named attribute if it exists, otherwise check for
- * a nested element of the same name. If both are specified it is an error,
- * but if neither is specified, just returns null.
+ * Get a text value from a named attribute if it exists, otherwise check for a nested element of the same name. If
+ * both are specified it is an error, but if neither is specified, just returns null.
*
- * @param element
- * a DOM node
- * @param name
- * the name of the property (attribute or child element)
- * @param parserContext
- * the current context
+ * @param element a DOM node
+ * @param name the name of the property (attribute or child element)
+ * @param parserContext the current context
* @return the text from the attribite or element or null
*/
- public static String getTextFromAttributeOrNestedElement(Element element,
- String name, ParserContext parserContext) {
+ public static String getTextFromAttributeOrNestedElement(Element element, String name, ParserContext parserContext) {
String attr = element.getAttribute(name);
Element childElement = DomUtils.getChildElementByTagName(element, name);
if (StringUtils.hasText(attr) && childElement != null) {
parserContext.getReaderContext().error(
- "Either an attribute or a child element can be specified for "
- + name + " but not both", element);
+ "Either an attribute or a child element can be specified for " + name + " but not both", element);
return null;
}
if (!StringUtils.hasText(attr) && childElement == null) {
@@ -237,8 +190,7 @@ public abstract class IntegrationNamespaceUtils {
return StringUtils.hasText(attr) ? attr : childElement.getTextContent();
}
- public static BeanComponentDefinition parseInnerHandlerDefinition(
- Element element, ParserContext parserContext) {
+ public static BeanComponentDefinition parseInnerHandlerDefinition(Element element, ParserContext parserContext) {
// parses out inner bean definition for concrete implementation if
// defined
List childElements = DomUtils.getChildElementsByTagName(element, "bean");
@@ -254,11 +206,11 @@ public abstract class IntegrationNamespaceUtils {
}
String ref = element.getAttribute(REF_ATTRIBUTE);
- Assert.isTrue(!(StringUtils.hasText(ref) && innerComponentDefinition != null), "Ambiguous definition. Inner bean " + (innerComponentDefinition == null
- ? innerComponentDefinition
- : innerComponentDefinition.getBeanDefinition().getBeanClassName())
- + " declaration and \"ref\" " + ref
- + " are not allowed together.");
+ Assert.isTrue(!(StringUtils.hasText(ref) && innerComponentDefinition != null),
+ "Ambiguous definition. Inner bean "
+ + (innerComponentDefinition == null ? innerComponentDefinition : innerComponentDefinition
+ .getBeanDefinition().getBeanClassName()) + " declaration and \"ref\" " + ref
+ + " are not allowed together.");
return innerComponentDefinition;
}
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/MethodInvokingInboundChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/MethodInvokingInboundChannelAdapterParser.java
index 9aa59c6426..35cb7b1d73 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/MethodInvokingInboundChannelAdapterParser.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/MethodInvokingInboundChannelAdapterParser.java
@@ -16,13 +16,19 @@
package org.springframework.integration.config.xml;
+import java.util.List;
+
import org.w3c.dom.Element;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
+import org.springframework.beans.factory.support.ManagedMap;
+import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
+import org.springframework.util.xml.DomUtils;
/**
* Parser for the <inbound-channel-adapter/> element.
@@ -33,26 +39,79 @@ public class MethodInvokingInboundChannelAdapterParser extends AbstractPollingIn
@Override
protected String parseSource(Element element, ParserContext parserContext) {
- BeanComponentDefinition bcDef = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
- String sourceRef = null;
- if (bcDef != null){
- sourceRef = bcDef.getBeanName();
- } else {
- sourceRef = element.getAttribute("ref");
+ BeanComponentDefinition innnerBeanDef = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
+ String sourceRef = element.getAttribute("ref");
+ String expressionString = element.getAttribute("expression");
+ if (innnerBeanDef != null) {
+ if (StringUtils.hasText(sourceRef)) {
+ parserContext.getReaderContext().error(
+ "inner bean and a 'ref' attribute are mutually exclusive options", element);
+ }
+ sourceRef = innnerBeanDef.getBeanName();
+ }
+ else if (StringUtils.hasText(expressionString)) {
+ if (StringUtils.hasText(sourceRef)) {
+ parserContext.getReaderContext().error(
+ "the 'expression' and 'ref' attributes are mutually exclusive options", element);
+ }
+ sourceRef = this.parseExpression(expressionString, element, parserContext);
}
if (!StringUtils.hasText(sourceRef)) {
- parserContext.getReaderContext().error("Either 'ref' attribute or inner-bean consumer definition is required.", element);
+ parserContext.getReaderContext().error("One of the following is required: " +
+ "'ref' attribute, 'expression' attribute, or an inner-bean definition.", element);
}
String methodName = element.getAttribute("method");
if (StringUtils.hasText(methodName)) {
- BeanDefinitionBuilder invokerBuilder = BeanDefinitionBuilder.genericBeanDefinition(
+ BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".endpoint.MethodInvokingMessageSource");
- invokerBuilder.addPropertyReference("object", sourceRef);
- invokerBuilder.addPropertyValue("methodName", methodName);
+ sourceBuilder.addPropertyReference("object", sourceRef);
+ sourceBuilder.addPropertyValue("methodName", methodName);
+ this.parseHeaderExpressions(sourceBuilder, element, parserContext);
sourceRef = BeanDefinitionReaderUtils.registerWithGeneratedName(
- invokerBuilder.getBeanDefinition(), parserContext.getRegistry());
+ sourceBuilder.getBeanDefinition(), parserContext.getRegistry());
}
return sourceRef;
}
+ private String parseExpression(String expressionString, Element element, ParserContext parserContext) {
+ BeanDefinitionBuilder sourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(
+ "org.springframework.integration.endpoint.ExpressionEvaluatingMessageSource");
+ RootBeanDefinition expressionDef = new RootBeanDefinition("org.springframework.integration.config.ExpressionFactoryBean");
+ expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expressionString);
+ sourceBuilder.addConstructorArgValue(expressionDef);
+ sourceBuilder.addConstructorArgValue(null); // TODO: add support for expectedType?
+ this.parseHeaderExpressions(sourceBuilder, element, parserContext);
+ return BeanDefinitionReaderUtils.registerWithGeneratedName(sourceBuilder.getBeanDefinition(), parserContext.getRegistry());
+ }
+
+ private void parseHeaderExpressions(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
+ List headerElements = DomUtils.getChildElementsByTagName(element, "header");
+ if (!CollectionUtils.isEmpty(headerElements)) {
+ ManagedMap headerExpressions = new ManagedMap();
+ for (Element headerElement : headerElements) {
+ String headerName = headerElement.getAttribute("name");
+ String headerValue = headerElement.getAttribute("value");
+ String headerExpression = headerElement.getAttribute("expression");
+ boolean hasValue = StringUtils.hasText(headerValue);
+ boolean hasExpression = StringUtils.hasText(headerExpression);
+ if (!(hasValue ^ hasExpression)) {
+ parserContext.getReaderContext().error("exactly one of 'value' or 'expression' is required on a header sub-element",
+ parserContext.extractSource(headerElement));
+ continue;
+ }
+ RootBeanDefinition expressionDef = null;
+ if (hasValue) {
+ expressionDef = new RootBeanDefinition("org.springframework.expression.common.LiteralExpression");
+ expressionDef.getConstructorArgumentValues().addGenericArgumentValue(headerValue);
+ }
+ else {
+ expressionDef = new RootBeanDefinition("org.springframework.integration.config.ExpressionFactoryBean");
+ expressionDef.getConstructorArgumentValues().addGenericArgumentValue(headerExpression);
+ }
+ headerExpressions.put(headerName, expressionDef);
+ }
+ builder.addPropertyValue("headerExpressions", headerExpressions);
+ }
+ }
+
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ResequencerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ResequencerParser.java
index 70f78a7648..c069a88bee 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ResequencerParser.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ResequencerParser.java
@@ -26,6 +26,7 @@ import org.w3c.dom.Element;
*
* @author Marius Bogoevici
* @author Dave Syer
+ * @author Iwein Fuld
*/
public class ResequencerParser extends AbstractConsumerEndpointParser {
@@ -84,6 +85,7 @@ public class ResequencerParser extends AbstractConsumerEndpointParser {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, DISCARD_CHANNEL_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_TIMEOUT_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, SEND_PARTIAL_RESULT_ON_EXPIRY_ATTRIBUTE);
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, RELEASE_PARTIAL_SEQUENCES_ATTRIBUTE);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
return builder;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ScheduledProducerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ScheduledProducerParser.java
deleted file mode 100644
index 77b095a3a1..0000000000
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ScheduledProducerParser.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2002-2010 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 java.util.List;
-
-import org.w3c.dom.Element;
-
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.ManagedMap;
-import org.springframework.beans.factory.support.RootBeanDefinition;
-import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
-import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
-import org.springframework.util.xml.DomUtils;
-
-/**
- * Parser for the <scheduled-producer> element.
- *
- * @author Mark Fisher
- * @since 2.0
- */
-public class ScheduledProducerParser extends AbstractSingleBeanDefinitionParser {
-
- @Override
- protected String getBeanClassName(Element element) {
- return IntegrationNamespaceUtils.BASE_PACKAGE + ".endpoint.ScheduledMessageProducer";
- }
-
- @Override
- protected boolean shouldGenerateIdAsFallback() {
- return true;
- }
-
- @Override
- protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
- String fixedDelay = element.getAttribute("fixed-delay");
- String fixedRate = element.getAttribute("fixed-rate");
- String cron = element.getAttribute("cron");
- String trigger = element.getAttribute("trigger");
- int numTriggers = 0;
- if (StringUtils.hasText(fixedDelay)) {
- RootBeanDefinition triggerDefinition = new RootBeanDefinition(
- "org.springframework.scheduling.support.PeriodicTrigger");
- triggerDefinition.getConstructorArgumentValues().addGenericArgumentValue(fixedDelay);
- builder.addConstructorArgValue(triggerDefinition);
- numTriggers++;
- }
- if (StringUtils.hasText(fixedRate)) {
- RootBeanDefinition triggerDefinition = new RootBeanDefinition(
- "org.springframework.scheduling.support.PeriodicTrigger");
- triggerDefinition.getConstructorArgumentValues().addGenericArgumentValue(fixedRate);
- triggerDefinition.getPropertyValues().add("fixedRate", Boolean.TRUE);
- builder.addConstructorArgValue(triggerDefinition);
- numTriggers++;
- }
- if (StringUtils.hasText(cron)) {
- RootBeanDefinition triggerDefinition = new RootBeanDefinition(
- "org.springframework.scheduling.support.CronTrigger");
- triggerDefinition.getConstructorArgumentValues().addGenericArgumentValue(cron);
- builder.addConstructorArgValue(triggerDefinition);
- numTriggers++;
- }
- if (StringUtils.hasText(trigger)) {
- builder.addConstructorArgReference(trigger);
- numTriggers++;
- }
- if (numTriggers != 1) {
- parserContext.getReaderContext().error("exactly one of the following trigger attributes must be provided: "
- + "fixed-delay, fixed-rate, cron, or trigger", parserContext.extractSource(element));
- return;
- }
- builder.addPropertyReference("outputChannel", element.getAttribute("channel"));
- builder.addConstructorArgValue(element.getAttribute("payload-expression"));
- IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
- List headerElements = DomUtils.getChildElementsByTagName(element, "header");
- if (!CollectionUtils.isEmpty(headerElements)) {
- ManagedMap headerExpressions = new ManagedMap();
- for (Element headerElement : headerElements) {
- String headerName = headerElement.getAttribute("name");
- String headerValue = headerElement.getAttribute("value");
- String headerExpression = headerElement.getAttribute("expression");
- boolean hasValue = StringUtils.hasText(headerValue);
- boolean hasExpression = StringUtils.hasText(headerExpression);
- if (!(hasValue ^ hasExpression)) {
- parserContext.getReaderContext().error("exactly one of 'value' or 'expression' is required on a header sub-element",
- parserContext.extractSource(headerElement));
- continue;
- }
- RootBeanDefinition expressionDef = null;
- if (hasValue) {
- expressionDef = new RootBeanDefinition("org.springframework.expression.common.LiteralExpression");
- expressionDef.getConstructorArgumentValues().addGenericArgumentValue(headerValue);
- }
- else {
- expressionDef = new RootBeanDefinition("org.springframework.integration.config.ExpressionFactoryBean");
- expressionDef.getConstructorArgumentValues().addGenericArgumentValue(headerExpression);
- }
- headerExpressions.put(headerName, expressionDef);
- }
- builder.addPropertyValue("headerExpressions", headerExpressions);
- }
- }
-
-}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/SplitterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/SplitterParser.java
index f28969c749..95e7530367 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/SplitterParser.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/SplitterParser.java
@@ -16,10 +16,15 @@
package org.springframework.integration.config.xml;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.w3c.dom.Element;
+
/**
* Parser for the <splitter/> element.
*
* @author Mark Fisher
+ * @author Iwein Fuld
*/
public class SplitterParser extends AbstractDelegatingConsumerEndpointParser {
@@ -33,4 +38,8 @@ public class SplitterParser extends AbstractDelegatingConsumerEndpointParser {
return true;
}
+ @Override
+ void postProcess(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "apply-sequence");
+ }
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java
index 4fbd85f8a6..48c2ee9d4f 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java
@@ -21,7 +21,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.integration.MessageChannel;
-import org.springframework.integration.context.metadata.MetadataPersister;
+import org.springframework.integration.context.metadata.MetadataStore;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.scheduling.TaskScheduler;
@@ -48,8 +48,8 @@ public abstract class IntegrationContextUtils {
public static final String DEFAULT_POLLER_METADATA_BEAN_NAME = "org.springframework.integration.context.defaultPollerMetadata";
- public static MetadataPersister getMetadataPersister(BeanFactory beanFactory) {
- return getBeanOfType(beanFactory, METADATA_PERSISTER_BEAN_NAME, MetadataPersister.class);
+ public static MetadataStore getMetadataPersister(BeanFactory beanFactory) {
+ return getBeanOfType(beanFactory, METADATA_PERSISTER_BEAN_NAME, MetadataStore.class);
}
public static MessageChannel getErrorChannel(BeanFactory beanFactory) {
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java
index a3d27f9bd7..2e063fb60b 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationObjectSupport.java
@@ -18,15 +18,12 @@ package org.springframework.integration.context;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
-import org.springframework.integration.context.metadata.MetadataPersister;
-import org.springframework.integration.context.metadata.PropertiesBasedMetadataPersister;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -50,8 +47,6 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo
* Logger that is available to subclasses
*/
protected final Log logger = LogFactory.getLog(getClass());
-
- private volatile MetadataPersister> metadataPersister;
private volatile String beanName;
@@ -119,27 +114,6 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo
return this.beanFactory;
}
- protected MetadataPersister getRequiredMetadataPersister() {
- if (this.metadataPersister == null && this.beanFactory != null) {
- this.metadataPersister = IntegrationContextUtils.getMetadataPersister(this.beanFactory);
- }
- if (this.metadataPersister == null) {
- PropertiesBasedMetadataPersister mp = new PropertiesBasedMetadataPersister();
-
- try {
- mp.afterPropertiesSet();
- }
- catch (Exception e) {
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- }
- throw new BeanInitializationException("failed to obtain reference to MetadataPersister strategy implementation.", e);
- }
- this.metadataPersister = mp;
- }
- return this.metadataPersister;
- }
-
protected TaskScheduler getTaskScheduler() {
if (this.taskScheduler == null && this.beanFactory != null) {
this.taskScheduler = IntegrationContextUtils.getTaskScheduler(this.beanFactory);
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/Orderable.java b/spring-integration-core/src/main/java/org/springframework/integration/context/Orderable.java
new file mode 100644
index 0000000000..a632036109
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/context/Orderable.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2010 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.context;
+
+import org.springframework.core.Ordered;
+
+/**
+ * Interface that extends {@link Ordered} while also exposing the
+ * {@link #setOrder(int)} as an interface-level so that it is avaiable
+ * on AOP proxies, etc.
+ *
+ * @author Mark Fisher
+ * @since 2.0
+ */
+public interface Orderable extends Ordered {
+
+ /**
+ * Set the order for this component.
+ */
+ void setOrder(int order);
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/FileBasedPropertiesStore.java b/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/FileBasedPropertiesStore.java
new file mode 100644
index 0000000000..2f2036a846
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/FileBasedPropertiesStore.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2002-2010 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.context.metadata;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+import org.springframework.util.DefaultPropertiesPersister;
+
+/**
+ * Properties file-based implementation of {@link MetadataStore}. To avoid conflicts
+ * each instance should be constructed with the unique key from which unique file name
+ * will be generated. The file name will be 'persistentKey' + ".last.entry".
+ * Files will be written to the 'java.io.tmpdir' + "/spring-integration/".
+ *
+ * @author Oleg Zhurakousky
+ * @since 2.0
+ */
+public class FileBasedPropertiesStore implements MetadataStore, InitializingBean{
+ private final Log logger = LogFactory.getLog(getClass());
+ private final DefaultPropertiesPersister persister = new DefaultPropertiesPersister();
+ private final String persistentKey;
+ private volatile File persistentFile;
+ private volatile String baseDirectory = System.getProperty("java.io.tmpdir") + "/spring-integration/";
+
+ public FileBasedPropertiesStore(String persistentKey){
+ Assert.notNull(persistentKey, "'persistentKey' must not be null");
+ this.persistentKey = persistentKey;
+ }
+
+ public void setBaseDirectory(String baseDirectory) {
+ this.baseDirectory = baseDirectory;
+ }
+
+ public String getBaseDirectory() {
+ return baseDirectory;
+ }
+
+ public void write(Properties metadata) {
+ FileOutputStream fo = null;
+ try {
+ fo = new FileOutputStream(persistentFile);
+ persister.store(metadata, fo, "Last feed entry");
+ }
+ catch (IOException e) {
+ // not fatal for the functionality of the component
+ logger.warn("Failed to persist feed entry. This may result in a duplicate " +
+ "feed entry after this component is restarted", e);
+ }
+ finally {
+ try {
+ if (fo != null){
+ fo.close();
+ }
+ }
+ catch (IOException e) {
+ // not fatal for the functionality of he component
+ logger.warn("Failed to close FileOutputStream to " + persistentFile.getAbsolutePath(), e);
+ }
+ }
+ }
+
+ public Properties load() {
+ Properties properties = new Properties();
+ FileInputStream iStream = null;
+ try {
+ iStream = new FileInputStream(persistentFile);
+ persister.load(properties, iStream);
+ } catch (Exception e) {
+ // not fatal for the functionality of the component
+ logger.warn("Failed to load feed entry from the persistent store. This may result in a duplicate " +
+ "feed entry after this component is restarted", e);
+ } finally {
+ try {
+ if (iStream != null){
+ iStream.close();
+ }
+ } catch (Exception e2) {
+ // non fatal
+ logger.warn("Failed to close FileInputStream for: " + persistentFile.getAbsolutePath());
+ }
+ }
+ return properties;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ String fileName = this.persistentKey + ".last.entry";
+ File baseDir = new File(baseDirectory);
+ baseDir.mkdirs();
+ persistentFile = new File(baseDir, fileName);
+ try {
+ if (!persistentFile.exists()){
+ persistentFile.createNewFile();
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to create metadata-store file '"
+ + persistentFile.getAbsolutePath() + "'", e);
+ }
+ }
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MapBasedMetadataPersister.java b/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MapBasedMetadataPersister.java
deleted file mode 100644
index be08702ee0..0000000000
--- a/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MapBasedMetadataPersister.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.springframework.integration.context.metadata;
-
-import org.springframework.util.Assert;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Simple in-memory implementation of teh {@link org.springframework.integration.context.metadata.MetadataPersister}
- * interface suitable for the use cases where it's assured that component only needs ephemeral metadata.
- *
- *
- * @author Josh Long
- * @param the type of objects to be stored as values. Keys will always be {@link String}
- */
-public class MapBasedMetadataPersister implements MetadataPersister {
-
- private ConcurrentHashMap metadataMap = new ConcurrentHashMap() ;
-
- public void write(String key, T value) {
- Assert.notNull( key != null , "key can't be null");
- Assert.notNull( value != null , "value can't be null");
- this.metadataMap.put( key, value);
- }
-
- public T read(String key) {
- return this.metadataMap.get(key);
- }
-}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MetadataPersister.java b/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MetadataPersister.java
deleted file mode 100644
index 6ba6464308..0000000000
--- a/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MetadataPersister.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.springframework.integration.context.metadata;
-
-
-/**
- * Envisioned as a strategy interface for persisting metadata from certain adapters / endpoints. Ideally,
- * there will be at least two options - one ephemeral persister (RAM-only) and one durable (*.ini based).
- *
- * This is used to give adapters / endpoints a place to store metadata to avoid duplicate delivery of messages, for example.
- *
- * @author Josh Long
- */
-public interface MetadataPersister {
- void write(String key, V value);
- V read(String key);
-}
diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java b/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MetadataStore.java
similarity index 50%
rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java
rename to spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MetadataStore.java
index aaa0bd632e..fa243ea61c 100644
--- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/MetadataStore.java
@@ -13,26 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.integration.monitor;
+package org.springframework.integration.context.metadata;
-import org.springframework.integration.MessageChannel;
-import org.springframework.integration.core.MessageHandler;
-import org.springframework.integration.core.MessageProducer;
-import org.springframework.jmx.export.annotation.ManagedResource;
+import java.util.Properties;
/**
+ * Strategy interface for persisting metadata from certain adapters / endpoints
+ * to avoid duplicate delivery of messages, for example.
+ *
+ * @author Josh Long
* @author Oleg Zhurakousky
* @since 2.0
- *
*/
-@ManagedResource
-public class SimpleMessageProducingHandlerMetrics extends SimpleMessageHandlerMetrics implements MessageProducer {
-
- public SimpleMessageProducingHandlerMetrics(MessageHandler handler) {
- super(handler);
- }
-
- public void setOutputChannel(MessageChannel outputChannel) {
- ((MessageProducer)this.getMessageHandler()).setOutputChannel(outputChannel);
- }
-}
\ No newline at end of file
+public interface MetadataStore {
+ /**
+ * Wil write propertoes to a persistent store
+ * @param metadata
+ */
+ void write(Properties metadata);
+ /**
+ * Will load Properties from the persistent store
+ * @return
+ */
+ Properties load();
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/PropertiesBasedMetadataPersister.java b/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/PropertiesBasedMetadataPersister.java
deleted file mode 100644
index 7809bd4df2..0000000000
--- a/spring-integration-core/src/main/java/org/springframework/integration/context/metadata/PropertiesBasedMetadataPersister.java
+++ /dev/null
@@ -1,220 +0,0 @@
-package org.springframework.integration.context.metadata;
-
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.config.PropertiesFactoryBean;
-import org.springframework.core.io.FileSystemResource;
-import org.springframework.core.io.Resource;
-import org.springframework.core.task.SimpleAsyncTaskExecutor;
-import org.springframework.util.Assert;
-
-import java.io.*;
-import java.util.*;
-import java.util.concurrent.Executor;
-
-
-/**
- * Implementation of {@link org.springframework.integration.context.metadata.MetadataPersister} that knows how to write metadata
- * to a {@link java.util.Properties} instance.
- *
- * @author Josh Long
- */
-public class PropertiesBasedMetadataPersister implements MetadataPersister, InitializingBean {
- /**
- * Used to queue the writes asynchronously
- */
- private Executor executor = new SimpleAsyncTaskExecutor();
-
- /**
- * Used to encapsulate acquisition of a {@link java.util.Properties} instance if it's prefered that we handled it on the client's behalf
- */
- private PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
-
- /**
- * guard for initialization and writes
- */
- private final Object monitor = new Object();
-
- /**
- * This would enable a background thread that would write as possible, but not block #write calls
- */
- private volatile boolean supportAsyncWrites;
-
- /**
- * An existing {@link java.util.Properties} file that we can read in at startup. This is utlimately forwarded to {@link org.springframework.beans.factory.config.PropertiesFactoryBean} on startup
- */
- private Properties properties;
-
- /**
- * Users can either provide a unique name and we can automatically setup #locationOfPropertiesOnDisk
- */
- private String uniqueName;
-
- /**
- * Or, a user can stipulate a {@link org.springframework.core.io.Resource} directly
- */
- private Resource locationOfPropertiesOnDisk;
- private Set bootstrapResources = new HashSet();
- private volatile File cachedLocationOfPropertiesFile;
-
- public PropertiesBasedMetadataPersister(Resource ultimateResourceToWhichToWriteFile) {
- setLocationOfPropertiesOnDisk(ultimateResourceToWhichToWriteFile);
- }
-
- @SuppressWarnings("unused")
- public PropertiesBasedMetadataPersister(String uniqueName) {
- this.uniqueName = uniqueName;
- }
-
- @SuppressWarnings("unused")
- public PropertiesBasedMetadataPersister() {
- }
-
- @SuppressWarnings("unused")
- public void setExecutor(Executor executor) {
- this.executor = executor;
- }
-
- public void setLocationOfPropertiesOnDisk(Resource locationOfPropertiesOnDisk) {
- this.locationOfPropertiesOnDisk = locationOfPropertiesOnDisk;
- }
-
- private File buildFileFromUniqueName() {
- File tmpDir = new File(System.getProperty("java.io.tmpdir"));
-
- String un = this.uniqueName + ".properties";
-
- return new File(tmpDir, un);
- }
-
- /**
- * Optional - if there's already a {@link java.util.Properties} instance in play than we can simply use that one.
- *
- * @param properties existing properties, just in case
- */
- @SuppressWarnings("unused")
- public void setProperties(Properties properties) {
- this.propertiesFactoryBean.setProperties(properties);
- }
-
- public void write(String key, String value) {
- Assert.notNull( key != null , "key can't be null");
- Assert.notNull( value != null , "value can't be null");
- synchronized (monitor) {
- long now = System.nanoTime();
- this.properties.setProperty(key, value);
-
- if (this.supportAsyncWrites) {
- this.executor.execute(new BackgroundWriterJob(now, key, value, this.properties));
- } else {
- doWriteToDisk(now, key, value, this.properties);
- }
- }
- }
-
- /**
- * This is required to ensure contiuity across restarts. It must be meaningful to a given application of a given component.
- *
- * @param uniqueName the unqiue name to use in constructing a {@link org.springframework.core.io.Resource} for the {@link java.util.Properties} file
- */
- @SuppressWarnings("unused")
- public void setUniqueName(String uniqueName) {
- this.uniqueName = uniqueName;
- }
-
- private void doWriteToDisk(long timestamp, String newKey, String newValue, Properties pro) {
- try {
- FileOutputStream fileOutputStream = null;
-
- try {
- fileOutputStream = new FileOutputStream (cachedLocationOfPropertiesFile);
- pro.store(fileOutputStream, this.uniqueName);
- } finally {
- if (fileOutputStream != null) {
- fileOutputStream.close();
- }
- }
- } catch (IOException e) {
- throw new RuntimeException("couldn't write " + this.properties + " on submission of " + newKey + "=" + newValue + " to disk at " + new Date(timestamp).toString());
- }
- }
-
- public String read(String key) {
- return this.properties.getProperty(key);
- }
-
- public void setSupportAsyncWrites(boolean supportAsyncWrites) {
- this.supportAsyncWrites = supportAsyncWrites;
- }
-
- public void afterPropertiesSet() throws Exception {
- synchronized (this.monitor) {
-
-
- if ((this.uniqueName == null) || this.uniqueName.trim().equals("")) {
- this.uniqueName = UUID.randomUUID().toString();
- }
-
- if ((this.locationOfPropertiesOnDisk == null) && (this.uniqueName == null)) {
- throw new RuntimeException("you must either specify a property file Resource or a uniqueName that can be used in generated a path that will be input into creating a Resource");
- }
-
- if ((this.locationOfPropertiesOnDisk == null)) {
- File pathOfPropertiesFileOnDisk = buildFileFromUniqueName();
- this.locationOfPropertiesOnDisk = new FileSystemResource(pathOfPropertiesFileOnDisk);
- }
-
- if (this.supportAsyncWrites) {
- Assert.notNull(this.executor, "'executorService' must be set on this bean or defined in the context");
- }
-
- if (this.locationOfPropertiesOnDisk.exists()) {
- this.bootstrapResources.add(locationOfPropertiesOnDisk);
- }
-
- this.cachedLocationOfPropertiesFile = this.locationOfPropertiesOnDisk.getFile();
-
- propertiesFactoryBean.setLocations(this.bootstrapResources.toArray(new Resource[bootstrapResources.size()]));
- // we take the existing Resources [] and use them to bootstrap a Properties instance when this component wakes up again
- propertiesFactoryBean.afterPropertiesSet();
- properties = propertiesFactoryBean.getObject();
- }
- }
-
- @SuppressWarnings("unused")
- public void setLocations(Resource[] locations) {
- for (int i = 0, locationsLength = locations.length; i < locationsLength; i++) {
- Resource r = locations[i];
- this.bootstrapResources.add(r);
- }
- }
-
- @SuppressWarnings("unused")
- public void setLocation(Resource location) {
- this.bootstrapResources.add(location);
- }
-
-
-
- /**
- * This class is used to ensure that the properies are persisted to the right place as soon as capacity / the task Scheduler allows
- */
- private class BackgroundWriterJob implements Runnable {
- private volatile Properties properties;
- private String key;
- private String value;
- private long now;
-
- public BackgroundWriterJob(long now, String key, String value, Properties properties) {
- this.properties = properties;
- this.now = now;
- this.key = key;
- this.value = value;
- }
-
- public void run() {
- synchronized (monitor) {
- doWriteToDisk(this.now, this.key, this.value, this.properties);
- }
- }
- }
-}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractMessageSource.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractMessageSource.java
new file mode 100644
index 0000000000..5ee0b4ec32
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractMessageSource.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2002-2010 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.endpoint;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.expression.Expression;
+import org.springframework.integration.Message;
+import org.springframework.integration.MessagingException;
+import org.springframework.integration.core.MessageSource;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.integration.util.AbstractExpressionEvaluator;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * @author Mark Fisher
+ * @since 2.0
+ */
+public abstract class AbstractMessageSource extends AbstractExpressionEvaluator implements MessageSource {
+
+ private volatile Map headerExpressions = Collections.emptyMap();
+
+
+ public void setHeaderExpressions(Map headerExpressions) {
+ this.headerExpressions = (headerExpressions != null)
+ ? headerExpressions : Collections.emptyMap();
+ }
+
+ @SuppressWarnings("unchecked")
+ public final Message receive() {
+ Message message = null;
+ Object result = this.doReceive();
+ if (result == null) {
+ return null;
+ }
+ Map headers = this.evaluateHeaders();
+ if (result instanceof Message>) {
+ try {
+ message = (Message) result;
+ }
+ catch (Exception e) {
+ throw new MessagingException("MessageSource returned unexpected type.", e);
+ }
+ if (!CollectionUtils.isEmpty(headers)) {
+ // create a new Message from this one in order to apply headers
+ MessageBuilder builder = MessageBuilder.fromMessage(message);
+ builder.copyHeaders(headers);
+ message = builder.build();
+ }
+ }
+ else {
+ T payload = null;
+ try {
+ payload = (T) result;
+ }
+ catch (Exception e) {
+ throw new MessagingException("MessageSource returned unexpected type.", e);
+ }
+ MessageBuilder builder = MessageBuilder.withPayload(payload);
+ if (!CollectionUtils.isEmpty(headers)) {
+ builder.copyHeaders(headers);
+ }
+ message = builder.build();
+ }
+ return message;
+ }
+
+ private Map evaluateHeaders() {
+ Map results = new HashMap();
+ for (Map.Entry entry : this.headerExpressions.entrySet()) {
+ Object headerValue = this.evaluateExpression(entry.getValue());
+ if (headerValue != null) {
+ results.put(entry.getKey(), headerValue);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Subclasses must implement this method. Typically the returned value will be the payload of
+ * type T, but the returned value may also be a Message instance whose payload is of type T.
+ */
+ protected abstract Object doReceive();
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/ExpressionEvaluatingMessageSource.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/ExpressionEvaluatingMessageSource.java
new file mode 100644
index 0000000000..47aeb7bde2
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/ExpressionEvaluatingMessageSource.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2010 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.endpoint;
+
+import org.springframework.expression.Expression;
+import org.springframework.util.Assert;
+
+/**
+ * @author Mark Fisher
+ * @since 2.0
+ */
+public class ExpressionEvaluatingMessageSource extends AbstractMessageSource {
+
+ private final Expression expression;
+
+ private final Class expectedType;
+
+
+ public ExpressionEvaluatingMessageSource(Expression expression, Class expectedType) {
+ Assert.notNull(expression, "expression must not be null");
+ this.expression = expression;
+ this.expectedType = expectedType;
+ }
+
+ public T doReceive() {
+ return this.evaluateExpression(this.expression, this.expectedType);
+ }
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MethodInvokingMessageSource.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MethodInvokingMessageSource.java
index 9f8f1cca89..1f00a94831 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MethodInvokingMessageSource.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MethodInvokingMessageSource.java
@@ -19,10 +19,8 @@ package org.springframework.integration.endpoint;
import java.lang.reflect.Method;
import org.springframework.beans.factory.InitializingBean;
-import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.MessageSource;
-import org.springframework.integration.message.GenericMessage;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@@ -32,7 +30,7 @@ import org.springframework.util.ReflectionUtils;
*
* @author Mark Fisher
*/
-public class MethodInvokingMessageSource implements MessageSource
-
commons-langcommons-lang2.5
@@ -28,7 +27,6 @@
rome-fetcher1.0.0
-
net.java.dev.romerome
diff --git a/spring-integration-feed/src/main/java/org/springframework/integration/feed/FeedEntryReaderMessageSource.java b/spring-integration-feed/src/main/java/org/springframework/integration/feed/FeedEntryReaderMessageSource.java
index 8c18aebcae..80e578bbec 100644
--- a/spring-integration-feed/src/main/java/org/springframework/integration/feed/FeedEntryReaderMessageSource.java
+++ b/spring-integration-feed/src/main/java/org/springframework/integration/feed/FeedEntryReaderMessageSource.java
@@ -1,123 +1,108 @@
+/*
+ * Copyright 2002-2010 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.feed;
-
-import com.sun.syndication.feed.synd.SyndEntry;
-import com.sun.syndication.feed.synd.SyndFeed;
-import org.springframework.context.Lifecycle;
-import org.springframework.integration.Message;
-import org.springframework.integration.context.IntegrationObjectSupport;
-import org.springframework.integration.context.metadata.MetadataPersister;
-import org.springframework.integration.core.MessageSource;
-import org.springframework.integration.support.MessageBuilder;
-import org.springframework.util.Assert;
-
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Properties;
+import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
+import org.springframework.integration.Message;
+import org.springframework.integration.context.IntegrationObjectSupport;
+import org.springframework.integration.context.metadata.FileBasedPropertiesStore;
+import org.springframework.integration.context.metadata.MetadataStore;
+import org.springframework.integration.core.MessageSource;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import com.sun.syndication.feed.synd.SyndEntry;
+import com.sun.syndication.feed.synd.SyndFeed;
/**
- * this is a slightly different use case than {@link org.springframework.integration.feed.FeedReaderMessageSource}.
- * This returns which entries are added, which is a more nuanced use case requiring some of our own caching.
- * NB: this does not somehow detect entry removal from a feed.
- *
+ * This implementation of {@link MessageSource} will produce individual {@link SyndEntry}s for a feed identified
+ * with 'feedUrl' attribute.
+ *
* @author Josh Long
* @author Mario Gray
+ * @author Oleg Zhurakousky
*/
-public class FeedEntryReaderMessageSource extends IntegrationObjectSupport implements MessageSource, Lifecycle {
+public class FeedEntryReaderMessageSource extends IntegrationObjectSupport implements MessageSource{
+ private volatile MetadataStore metadataStore;
- private volatile ConcurrentLinkedQueue entries;
- private volatile MetadataPersister persister;
+ private volatile Properties lastPersistentEntry = new Properties();
+ private volatile Queue entries = new ConcurrentLinkedQueue();
private volatile FeedReaderMessageSource feedReaderMessageSource;
private final Object monitor = new Object();
- private String feedMetadataIdKey;
- private String feedUrl;
- private volatile boolean running;
-
- public boolean isRunning() {
- return running;
- }
-
- public void setRunning(boolean running) {
- this.running = running;
- }
- // private Queue entries;
+ private volatile String feedMetadataIdKey;
+ private volatile String persistentIdentifier;
+ private volatile boolean initialized;
private volatile long lastTime = -1;
- public FeedEntryReaderMessageSource() {
- // this.entries = new ConcurrentSkipListSet(new MyComparator());
- this.entries = new ConcurrentLinkedQueue();
- }
-
- public void start() {
- this.feedReaderMessageSource.start();
- this.setRunning(true);
-
- }
-
-
- private long sortId(SyndEntry entry) {
- return entry.getPublishedDate().getTime();
- }
-
-
- @Override
- protected void onInit() throws Exception {
-
- this.persister = this.getRequiredMetadataPersister();
-
- Assert.notNull(this.feedUrl, "the feedUrl can't be null");
- this.feedReaderMessageSource = new FeedReaderMessageSource();
- this.feedReaderMessageSource.setFeedUrl(this.feedUrl);
- this.feedReaderMessageSource.setBeanFactory(this.getBeanFactory());
- this.feedReaderMessageSource.setBeanName(this.getComponentName());
- this.feedReaderMessageSource.afterPropertiesSet();
-
- // setup persistence of metadata
- this.feedMetadataIdKey = FeedEntryReaderMessageSource.class.getName() + "#" + feedUrl;
- String lastTime = (String) this.persister.read(this.feedMetadataIdKey);
- if (lastTime != null && !lastTime.trim().equalsIgnoreCase("")) {
- this.lastTime = Long.parseLong(lastTime);
+ private Comparator syndEntryComparator = new Comparator() {
+ public int compare(SyndEntry syndEntry, SyndEntry syndEntry1) {
+ long x = syndEntry.getPublishedDate().getTime() -
+ syndEntry1.getPublishedDate().getTime();
+ if (x < -1) {
+ return -1;
+ }
+ else if (x > 1) {
+ return 1;
+ }
+ return 0;
}
- }
+ };
+
+ public FeedEntryReaderMessageSource(FeedReaderMessageSource feedReaderMessageSource) {
+ Assert.notNull(feedReaderMessageSource, "'feedReaderMessageSource' must not be null");
+ this.feedReaderMessageSource = feedReaderMessageSource;
+ }
- public void stop() {
- this.feedReaderMessageSource.stop();
- this.setRunning(false);
+ public void setPersistentIdentifier(String persistentIdentifier) {
+ this.persistentIdentifier = persistentIdentifier;
+ }
+
+ public void setMetadataStore(MetadataStore metadataStore) {
+ this.metadataStore = metadataStore;
+ }
+
+ public String getComponentType(){
+ return "feed:inbound-channel-adapter";
}
-
public Message receive() {
- SyndEntry se = receiveSyndEntry();
+ Assert.isTrue(this.initialized, "'FeedEntryReaderMessageSource' must be initialized before it can produce Messages");
+ SyndEntry se = doReceieve();
if (se == null) {
return null;
}
return MessageBuilder.withPayload(se).build();
}
-
- int longToCompare(long l) {
- if (l < -1) return -1;
- if (l > 1) return 1;
- return 0;
- }
-
- private Comparator syndEntryComparator = new Comparator() {
- public int compare(SyndEntry syndEntry, SyndEntry syndEntry1) {
- long x = sortId(syndEntry) - sortId(syndEntry1);
- return longToCompare(x);
- }
- };
-
+
@SuppressWarnings("unchecked")
- public SyndEntry receiveSyndEntry() {
- synchronized (this.monitor) { // priority goes to the backlog
- SyndEntry nextUp = pollAndCache();
+ private SyndEntry doReceieve() {
+ SyndEntry nextUp = null;
+ synchronized (this.monitor) {
+ nextUp = pollAndCache();
if (nextUp != null) {
return nextUp;
}
-
// otherwise, fill the backlog up
SyndFeed syndFeed = this.feedReaderMessageSource.receiveSyndFeed();
if (syndFeed != null) {
@@ -125,43 +110,56 @@ public class FeedEntryReaderMessageSource extends IntegrationObjectSupport imple
if (null != feedEntries) {
Collections.sort(feedEntries, syndEntryComparator);
for (SyndEntry se : feedEntries) {
- System.out.println("se: " + se.getPublishedDate().getTime());
- long sort = this.sortId(se);
- if (sort > this.lastTime)
- entries.add(se);
+ long publishedTime = se.getPublishedDate().getTime();
+ if (publishedTime > this.lastTime){
+ entries.add(se);
+ }
}
}
}
-
- return pollAndCache();
+ nextUp = pollAndCache();
}
+ return nextUp;
+ }
+
+ @Override
+ protected void onInit() throws Exception {
+ if (StringUtils.hasText(this.persistentIdentifier)){
+ if (this.metadataStore == null){
+ logger.info("Creating FileBasedPropertiesStore");
+ metadataStore = new FileBasedPropertiesStore(this.persistentIdentifier);
+ ((FileBasedPropertiesStore)metadataStore).afterPropertiesSet();
+ }
+ lastPersistentEntry = metadataStore.load();
+ }
+ else {
+ logger.info("Your '" + this.getComponentType() + "' is anonymous (no ID attribute), therefore no feed entries will be persisted " +
+ "which may result in a duplicate feed entries once this adapter is restarted");
+ }
+
+ this.feedMetadataIdKey = this.getComponentType() + "@" + this.getComponentName() +
+ "#" + feedReaderMessageSource.getFeedUrl();
+ String keyTime = (String) this.lastPersistentEntry.get(this.feedMetadataIdKey);
+ if (StringUtils.hasText(keyTime)){
+ this.lastTime = Long.parseLong(keyTime);
+ }
+ this.initialized = true;
}
-
- private SyndEntry pollAndCache() {
+ private SyndEntry pollAndCache() {
SyndEntry next = this.entries.poll();
- if (null == next) return null;
- this.lastTime = sortId(next);
- this.persister.write(this.feedMetadataIdKey, this.lastTime + "");
+
+ if (next == null) {
+ return null;
+ }
+
+ this.lastTime = next.getPublishedDate().getTime();
+ this.lastPersistentEntry.put(this.feedMetadataIdKey, this.lastTime + "");
+
+ if (metadataStore != null){
+ metadataStore.write(this.lastPersistentEntry);
+ }
+
return next;
}
-
-
- public String getFeedUrl() {
- return feedUrl;
- }
-
- public void setFeedUrl(final String feedUrl) {
- this.feedUrl = feedUrl;
- }
-
-
- class MyComparator implements Comparator {
- public int compare(final SyndEntry syndEntry, final SyndEntry syndEntry1) {
- long val = sortId(syndEntry) - sortId(syndEntry1);
- if (val > 0) return 1;
- if (val < 0) return -1;
- return 0;
- }
- }
}
\ No newline at end of file
diff --git a/spring-integration-feed/src/main/java/org/springframework/integration/feed/FeedReaderMessageSource.java b/spring-integration-feed/src/main/java/org/springframework/integration/feed/FeedReaderMessageSource.java
index d5f7517153..7f1dfa5910 100644
--- a/spring-integration-feed/src/main/java/org/springframework/integration/feed/FeedReaderMessageSource.java
+++ b/spring-integration-feed/src/main/java/org/springframework/integration/feed/FeedReaderMessageSource.java
@@ -1,119 +1,93 @@
+/*
+ * Copyright 2002-2010 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.feed;
-import com.sun.syndication.feed.synd.SyndFeed;
-import com.sun.syndication.fetcher.FetcherEvent;
-import com.sun.syndication.fetcher.FetcherListener;
-import com.sun.syndication.fetcher.impl.FeedFetcherCache;
-import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache;
-import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.context.Lifecycle;
-import org.springframework.integration.Message;
-import org.springframework.integration.context.IntegrationObjectSupport;
-import org.springframework.integration.context.metadata.MetadataPersister;
-import org.springframework.integration.core.MessageSource;
-import org.springframework.integration.support.MessageBuilder;
-import org.springframework.util.Assert;
-
import java.net.URL;
import java.util.concurrent.ConcurrentLinkedQueue;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.integration.Message;
+import org.springframework.integration.MessagingException;
+import org.springframework.integration.context.IntegrationObjectSupport;
+import org.springframework.integration.core.MessageSource;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.util.Assert;
+
+import com.sun.syndication.feed.synd.SyndFeed;
+import com.sun.syndication.fetcher.FetcherEvent;
+import com.sun.syndication.fetcher.FetcherListener;
+import com.sun.syndication.fetcher.impl.AbstractFeedFetcher;
+import com.sun.syndication.fetcher.impl.FeedFetcherCache;
+import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache;
+import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
+
/**
- * The idea behind this class is that {@link org.springframework.integration.core.MessageSource#receive()} will only
- * return a {@link SyndFeed} when the event listener tells us that a feed has been updated. If we can ascertain that
- * it's been updated, then we can add the item to the {@link java.util.Queue} implementation.
+ * This implementation of {@link MessageSource} will produce {@link SyndFeed} for a feed identified
+ * with 'feedUrl' attribute.
*
* @author Josh Long
* @author Mario Gray
+ * @author Oleg Zhurakousky
*/
public class FeedReaderMessageSource extends IntegrationObjectSupport
- implements InitializingBean, Lifecycle, MessageSource {
- private volatile boolean running;
- private volatile String feedUrl;
- private volatile URL feedURLObject;
+ implements InitializingBean, MessageSource {
+
+ private final AbstractFeedFetcher fetcher;
+ private final Object syndFeedMonitor = new Object();
+
+ private volatile URL feedUrl;
private volatile FeedFetcherCache fetcherCache;
- private volatile HttpURLFeedFetcher fetcher;
- private volatile ConcurrentLinkedQueue syndFeeds;
+ private volatile ConcurrentLinkedQueue syndFeeds = new ConcurrentLinkedQueue();
private volatile MyFetcherListener myFetcherListener;
-
- public FeedReaderMessageSource() {
- syndFeeds = new ConcurrentLinkedQueue();
+
+
+ public FeedReaderMessageSource(URL feedUrl) {
+ this.feedUrl = feedUrl;
+ if (feedUrl.getProtocol().equals("file")){
+ fetcher = new FileUrlFeedFetcher();
+ }
+ else if (feedUrl.getProtocol().equals("http")){
+ fetcherCache = HashMapFeedInfoCache.getInstance();
+ fetcher = new HttpURLFeedFetcher(fetcherCache);
+ }
+ else{
+ throw new IllegalArgumentException("Unsupported URL protocol: " + feedUrl.getProtocol());
+ }
}
-
- private volatile MetadataPersister persister;
-
- @Override
- protected void onInit() throws Exception {
-
- this.persister = this.getRequiredMetadataPersister();
-
- myFetcherListener = new MyFetcherListener();
- fetcherCache = HashMapFeedInfoCache.getInstance();
-
- fetcher = new HttpURLFeedFetcher(fetcherCache);
-
- // fetcher.set
- fetcher.addFetcherEventListener(myFetcherListener);
- Assert.notNull(this.feedUrl, "the feedURL can't be null");
- feedURLObject = new URL(this.feedUrl);
-/*
- String id = FeedReaderMessageSource.class.getName() + "#" + feedUrl;
-
- StringBuffer stringBuffer = new StringBuffer();
-
- for (char c : id.toCharArray())
- if (Character.isDigit(c) || Character.isLetter(c))
- stringBuffer.append(c);
- id = stringBuffer.toString();
-
- this.feedMetadataIdKey = id;
-
-
- long lastTimeNo = -1;
- String lastTime = (String) this.persister.read(this.feedMetadataIdKey);
- if (lastTime != null && !lastTime.trim().equalsIgnoreCase("")) {
- lastTimeNo = Long.parseLong(lastTime);
- this.lastTime = lastTimeNo;
- }*/
-
+
+ public URL getFeedUrl() {
+ return feedUrl;
}
-
- private volatile long lastTime = -1;
-
- public void start() {
- this.running = true;
- }
-
- public void stop() {
- this.running = false;
- }
-
- private String feedMetadataIdKey;
- private final Object syndFeedMonitor = new Object();
-
+
public SyndFeed receiveSyndFeed() {
SyndFeed returnedSyndFeed = null;
try {
synchronized (syndFeedMonitor) {
- fetcher.retrieveFeed(this.feedURLObject);
+ returnedSyndFeed = fetcher.retrieveFeed(this.feedUrl);
logger.debug("attempted to retrieve feed '" + this.feedUrl + "'");
- returnedSyndFeed = syndFeeds.poll(); // there wont be things whose pub date is < than the lastTime
- if (null == returnedSyndFeed) {
+ if (returnedSyndFeed == null) {
logger.debug("no feeds updated, return null!");
return null;
}
- // so its OK to update the lastTime
- //
-
- /* this.lastTime = sortId(returnedSyndFeed);if (null != this.persister)
- this.persister.write(this.feedMetadataIdKey, this.lastTime + "");
-*/
}
- } catch (Throwable e) {
- logger.debug("Exception thrown when trying to retrive feed at url '" + this.feedURLObject + "'", e);
+ } catch (Exception e) {
+ throw new MessagingException("Exception thrown when trying to retrive feed at url '" + this.feedUrl + "'", e);
}
return returnedSyndFeed;
@@ -126,22 +100,18 @@ public class FeedReaderMessageSource extends IntegrationObjectSupport
return null;
}
- return MessageBuilder.withPayload(syndFeed).setHeader(FeedConstants.FEED_URL, this.feedURLObject).build();
+ return MessageBuilder.withPayload(syndFeed).setHeader(FeedConstants.FEED_URL, this.feedUrl).build();
}
- public boolean isRunning() {
- return this.running;
+ @Override
+ protected void onInit() throws Exception {
+
+
+
+ fetcher.addFetcherEventListener(myFetcherListener);
+ Assert.notNull(this.feedUrl, "the feedURL can't be null");
}
-
- public String getFeedUrl() {
- return feedUrl;
- }
-
- public void setFeedUrl(final String feedUrl) {
- this.feedUrl = feedUrl;
- }
-
-
+
class MyFetcherListener implements FetcherListener {
/**
* @see com.sun.syndication.fetcher.FetcherListener#fetcherEvent(com.sun.syndication.fetcher.FetcherEvent)
@@ -153,8 +123,7 @@ public class FeedReaderMessageSource extends IntegrationObjectSupport
logger.debug("\tEVENT: Feed Polled. URL = " + event.getUrlString());
} else if (FetcherEvent.EVENT_TYPE_FEED_RETRIEVED.equals(eventType)) {
logger.debug("\tEVENT: Feed Retrieved. URL = " + event.getUrlString());
- // if (sortId(event.getFeed()) > lastTime) // its true if the lastTime is -1 || N
- syndFeeds.add(event.getFeed());
+ syndFeeds.add(event.getFeed());
} else if (FetcherEvent.EVENT_TYPE_FEED_UNCHANGED.equals(eventType)) {
logger.debug("\tEVENT: Feed Unchanged. URL = " + event.getUrlString());
}
diff --git a/spring-integration-feed/src/main/java/org/springframework/integration/feed/FileUrlFeedFetcher.java b/spring-integration-feed/src/main/java/org/springframework/integration/feed/FileUrlFeedFetcher.java
new file mode 100644
index 0000000000..1d3e2abcf2
--- /dev/null
+++ b/spring-integration-feed/src/main/java/org/springframework/integration/feed/FileUrlFeedFetcher.java
@@ -0,0 +1,109 @@
+/*
+* Copyright 2010 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.feed;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.zip.GZIPInputStream;
+
+import com.sun.syndication.feed.synd.SyndFeed;
+import com.sun.syndication.fetcher.FetcherEvent;
+import com.sun.syndication.fetcher.FetcherException;
+import com.sun.syndication.fetcher.impl.AbstractFeedFetcher;
+import com.sun.syndication.fetcher.impl.SyndFeedInfo;
+import com.sun.syndication.io.FeedException;
+import com.sun.syndication.io.SyndFeedInput;
+import com.sun.syndication.io.XmlReader;
+
+/**
+ * @author Oleg Zhurakousky
+ * @since 2.0
+ */
+public class FileUrlFeedFetcher extends AbstractFeedFetcher {
+
+ /* (non-Javadoc)
+ * @see com.sun.syndication.fetcher.FeedFetcher#retrieveFeed(java.net.URL)
+ */
+ public SyndFeed retrieveFeed(URL feedUrl) throws IllegalArgumentException,
+ IOException, FeedException, FetcherException {
+ if (feedUrl == null) {
+ throw new IllegalArgumentException("null is not a valid URL");
+ }
+
+ URLConnection connection = feedUrl.openConnection();
+
+ SyndFeedInfo syndFeedInfo = new SyndFeedInfo();
+ retrieveAndCacheFeed(feedUrl, syndFeedInfo, connection);
+ return syndFeedInfo.getSyndFeed();
+ }
+
+ protected void retrieveAndCacheFeed(URL feedUrl, SyndFeedInfo syndFeedInfo, URLConnection connection) throws IllegalArgumentException, FeedException, FetcherException, IOException {
+ resetFeedInfo(feedUrl, syndFeedInfo, connection);
+ }
+
+ protected void resetFeedInfo(URL orignalUrl, SyndFeedInfo syndFeedInfo, URLConnection connection) throws IllegalArgumentException, IOException, FeedException {
+ // need to always set the URL because this may have changed due to 3xx redirects
+ syndFeedInfo.setUrl(connection.getURL());
+
+ // the ID is a persistant value that should stay the same even if the URL for the
+ // feed changes (eg, by 3xx redirects)
+ syndFeedInfo.setId(orignalUrl.toString());
+
+ // This will be 0 if the server doesn't support or isn't setting the last modified header
+ syndFeedInfo.setLastModified(new Long(connection.getLastModified()));
+
+ // get the contents
+ InputStream inputStream = null;
+ try {
+ inputStream = connection.getInputStream();
+ SyndFeed syndFeed = getSyndFeedFromStream(inputStream, connection);
+ syndFeedInfo.setSyndFeed(syndFeed);
+ } finally {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ }
+ }
+ private SyndFeed getSyndFeedFromStream(InputStream inputStream, URLConnection connection) throws IOException, IllegalArgumentException, FeedException {
+ SyndFeed feed = readSyndFeedFromStream(inputStream, connection);
+ fireEvent(FetcherEvent.EVENT_TYPE_FEED_RETRIEVED, connection, feed);
+ return feed;
+ }
+ private SyndFeed readSyndFeedFromStream(InputStream inputStream, URLConnection connection) throws IOException, IllegalArgumentException, FeedException {
+ BufferedInputStream is;
+ if ("gzip".equalsIgnoreCase(connection.getContentEncoding())) {
+ // handle gzip encoded content
+ is = new BufferedInputStream(new GZIPInputStream(inputStream));
+ } else {
+ is = new BufferedInputStream(inputStream);
+ }
+
+ XmlReader reader = null;
+ if (connection.getHeaderField("Content-Type") != null) {
+ reader = new XmlReader(is, connection.getHeaderField("Content-Type"), true);
+ } else {
+ reader = new XmlReader(is, true);
+ }
+
+ SyndFeedInput syndFeedInput = new SyndFeedInput();
+ syndFeedInput.setPreserveWireFeed(isPreserveWireFeed());
+
+ return syndFeedInput.build(reader);
+ }
+}
diff --git a/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParser.java b/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParser.java
index 32bcd24e6e..1fffb8781a 100644
--- a/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParser.java
+++ b/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParser.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2002-2010 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.feed.config;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -5,39 +20,34 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
-import org.springframework.integration.feed.FeedEntryReaderMessageSource;
-import org.springframework.integration.feed.FeedReaderMessageSource;
+import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Handles parsing the configuration for the feed inbound channel adapter.
*
* @author Josh Long
+ * @author Oleg Zhurakousky
*/
public class FeedMessageSourceBeanDefinitionParser extends AbstractPollingInboundChannelAdapterParser {
-
- private String packageName = FeedReaderMessageSource.class.getPackage().getName();
-
-
@Override
protected String parseSource(final Element element, final ParserContext parserContext) {
- String pftoe = (element.getAttribute("prefer-updated-feed-to-entries"));
- pftoe = pftoe == null ? "false" : pftoe.trim().toLowerCase();
-
- boolean preferFeed = pftoe.equalsIgnoreCase(Boolean.TRUE.toString().toLowerCase());
- String className = this.packageName + "." + (preferFeed ?
- FeedReaderMessageSource.class.getSimpleName() :
- FeedEntryReaderMessageSource.class.getSimpleName()
- );
- BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(className);
- builder.addPropertyValue("feedUrl", element.getAttribute("feed"));
-
- if (!preferFeed) {
- IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "backlog-cache-size", "maximumBacklogCacheSize");
+ BeanDefinitionBuilder feedEntryBuilder =
+ BeanDefinitionBuilder.genericBeanDefinition("org.springframework.integration.feed.FeedEntryReaderMessageSource");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(feedEntryBuilder, element, "id", "persistentIdentifier");
+ BeanDefinitionBuilder feedBuilder =
+ BeanDefinitionBuilder.genericBeanDefinition("org.springframework.integration.feed.FeedReaderMessageSource");
+ feedBuilder.addConstructorArgValue(element.getAttribute("feed-url"));
+
+ String metadataStoreStrategy = element.getAttribute("metadata-store");
+ if (StringUtils.hasText(metadataStoreStrategy)){
+ feedEntryBuilder.addPropertyReference("metadataStore", metadataStoreStrategy);
}
-
- return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry());
+
+ feedEntryBuilder.addConstructorArgValue(feedBuilder.getBeanDefinition());
+
+ return BeanDefinitionReaderUtils.registerWithGeneratedName(feedEntryBuilder.getBeanDefinition(), parserContext.getRegistry());
}
}
\ No newline at end of file
diff --git a/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedNamespaceHandler.java b/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedNamespaceHandler.java
index ace8004450..5ffa3ba54d 100644
--- a/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedNamespaceHandler.java
+++ b/spring-integration-feed/src/main/java/org/springframework/integration/feed/config/FeedNamespaceHandler.java
@@ -1,33 +1,26 @@
-package org.springframework.integration.feed.config;
/*
-* Copyright 2010 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.
-*/
-
-
+ * Copyright 2002-2010 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.feed.config;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
- * This is a rather tricky one. I've decided it's best to not get cute about it and to expose *one*
- * inbound-channel-adapter. The adapter will let the user pick which type of updated object they'd like to
- * return. By default it'll return new {@link com.sun.syndication.feed.synd.SyndEntry} objects (which represent
- * individual, new entries in a given feed). One adapter will return updated {@link
- * com.sun.syndication.feed.synd.SyndFeed} objects, or it can return updated {@link
- * com.sun.syndication.feed.synd.SyndEntry} objects.
- *
+ * NamespaceHandler for FEED module
+ *
* @author Josh Long
*/
public class FeedNamespaceHandler extends NamespaceHandlerSupport {
@@ -35,6 +28,4 @@ public class FeedNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("inbound-channel-adapter", new FeedMessageSourceBeanDefinitionParser());
}
-
-
}
\ No newline at end of file
diff --git a/spring-integration-feed/src/main/resources/org/springframework/integration/feed/config/spring-integration-feed-2.0.xsd b/spring-integration-feed/src/main/resources/org/springframework/integration/feed/config/spring-integration-feed-2.0.xsd
index c3f725ec50..c9aa79a62c 100644
--- a/spring-integration-feed/src/main/resources/org/springframework/integration/feed/config/spring-integration-feed-2.0.xsd
+++ b/spring-integration-feed/src/main/resources/org/springframework/integration/feed/config/spring-integration-feed-2.0.xsd
@@ -27,6 +27,7 @@
+
@@ -36,17 +37,26 @@
-
-
-
-
-
-
-
-
+
+
+
+ Allows you to provide cusom implementation of 'org.springframework.integration.context.metadata.MetadataStore'
+ to persist the state of the retrieved feeds to aviod duplicates between restarts.
+
+
+
+
+
+
+
+
+
+
+
+ Allows you to specify URL for RSS/ATOM feed
+
+
+
diff --git a/spring-integration-core/src/test/java/log4j.properties b/spring-integration-feed/src/test/java/log4j.properties
similarity index 81%
rename from spring-integration-core/src/test/java/log4j.properties
rename to spring-integration-feed/src/test/java/log4j.properties
index 941cbe4822..0c10e7ac64 100644
--- a/spring-integration-core/src/test/java/log4j.properties
+++ b/spring-integration-feed/src/test/java/log4j.properties
@@ -5,4 +5,4 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%c{1}: %m%n
log4j.category.org.springframework.integration=WARN
-log4j.category.org.springframework.integration.file=WARN
+log4j.category.org.springframework.integration.feed=DEBUG
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/FeedEntryReaderMessageSourceTests.java b/spring-integration-feed/src/test/java/org/springframework/integration/feed/FeedEntryReaderMessageSourceTests.java
new file mode 100644
index 0000000000..a1226963c8
--- /dev/null
+++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/FeedEntryReaderMessageSourceTests.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2002-2010 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.feed;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.integration.Message;
+
+import com.sun.syndication.feed.synd.SyndEntry;
+import com.sun.syndication.feed.synd.SyndFeed;
+
+/**
+ * @author Oleg Zhurakousky
+ *
+ */
+public class FeedEntryReaderMessageSourceTests {
+ @Before
+ public void prepare(){
+ File persisterFile = new File(System.getProperty("java.io.tmpdir") + "/spring-integration/", "feedReader.last.entry");
+ if (persisterFile.exists()){
+ persisterFile.delete();
+ }
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testFailureWhenNotInitialized(){
+ FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(mock(FeedReaderMessageSource.class));
+ feedEntrySource.receive();
+ }
+
+ @Test
+ public void testReceieveFeedWithNoEntries(){
+ FeedReaderMessageSource feedReaderSource = mock(FeedReaderMessageSource.class);
+ SyndFeed feed = mock(SyndFeed.class);
+ when(feedReaderSource.receiveSyndFeed()).thenReturn(feed);
+ FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
+ feedEntrySource.setPersistentIdentifier("feedReader");
+ feedEntrySource.afterPropertiesSet();
+ assertNull(feedEntrySource.receive());
+ }
+ @Test
+ public void testReceieveFeedWithEntriesSorted(){
+ FeedReaderMessageSource feedReaderSource = mock(FeedReaderMessageSource.class);
+ SyndFeed feed = mock(SyndFeed.class);
+ SyndEntry entry1 = mock(SyndEntry.class);
+ SyndEntry entry2 = mock(SyndEntry.class);
+ when(entry1.getPublishedDate()).thenReturn(new Date(System.currentTimeMillis()));
+ when(entry2.getPublishedDate()).thenReturn(new Date(System.currentTimeMillis()-10000));
+
+ List entries = new ArrayList();
+ entries.add(entry2);
+ entries.add(entry1);
+ when(feed.getEntries()).thenReturn(entries);
+ when(feedReaderSource.receiveSyndFeed()).thenReturn(feed);
+
+ FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
+ feedEntrySource.setPersistentIdentifier("feedReader");
+ feedEntrySource.afterPropertiesSet();
+ Message entryMessage = feedEntrySource.receive();
+ assertEquals(entry2, entryMessage.getPayload());
+ entryMessage = feedEntrySource.receive();
+ assertEquals(entry1, entryMessage.getPayload());
+ reset(feed);
+ entryMessage = feedEntrySource.receive();
+ assertNull(entryMessage);
+ }
+ // will test, that last feed entry is remembered between the sessions
+ // and no duplicate entries are retrieved
+ @Test
+ public void testReceieveFeedWithRealEntriesAndRepeatWithPersistentIdentifier() throws Exception{
+ FeedReaderMessageSource feedReaderSource =
+ new FeedReaderMessageSource(new URL("file:src/test/java/org/springframework/integration/feed/sample.rss"));
+
+ FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
+ feedEntrySource.setPersistentIdentifier("feedReader");
+ feedEntrySource.afterPropertiesSet();
+ SyndEntry entry1 = feedEntrySource.receive().getPayload();
+ SyndEntry entry2 = feedEntrySource.receive().getPayload();
+ SyndEntry entry3 = feedEntrySource.receive().getPayload();
+ assertNull(feedEntrySource.receive()); // only 3 entries in the test feed
+
+ assertEquals("Spring Integration download", entry1.getTitle().trim());
+ assertEquals(1266088337000L, entry1.getPublishedDate().getTime());
+
+ assertEquals("Check out Spring Integration forums", entry2.getTitle().trim());
+ assertEquals(1268469501000L, entry2.getPublishedDate().getTime());
+
+ assertEquals("Spring Integration adapters", entry3.getTitle().trim());
+ assertEquals(1272044098000L, entry3.getPublishedDate().getTime());
+
+ // now test that what's been read is no longer retrieved
+ feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
+ feedEntrySource.setPersistentIdentifier("feedReader");
+ feedEntrySource.afterPropertiesSet();
+ assertNull(feedEntrySource.receive());
+ assertNull(feedEntrySource.receive());
+ assertNull(feedEntrySource.receive());
+ }
+ // will test, that last feed entry is NOT remembered between the sessions, since
+ // persister is not used due to the lack of persistentIdentifier (id attribute in xml)
+ // and the same entries are retrieved again
+ @Test
+ public void testReceieveFeedWithRealEntriesAndRepeatNoPersistentIdentifier() throws Exception{
+ FeedReaderMessageSource feedReaderSource =
+ new FeedReaderMessageSource(new URL("file:src/test/java/org/springframework/integration/feed/sample.rss"));
+
+ FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
+ feedEntrySource.afterPropertiesSet();
+ SyndEntry entry1 = feedEntrySource.receive().getPayload();
+ SyndEntry entry2 = feedEntrySource.receive().getPayload();
+ SyndEntry entry3 = feedEntrySource.receive().getPayload();
+ assertNull(feedEntrySource.receive()); // only 3 entries in the test feed
+
+ assertEquals("Spring Integration download", entry1.getTitle().trim());
+ assertEquals(1266088337000L, entry1.getPublishedDate().getTime());
+
+ assertEquals("Check out Spring Integration forums", entry2.getTitle().trim());
+ assertEquals(1268469501000L, entry2.getPublishedDate().getTime());
+
+ assertEquals("Spring Integration adapters", entry3.getTitle().trim());
+ assertEquals(1272044098000L, entry3.getPublishedDate().getTime());
+
+ // UNLIKE the previous test
+ // now test that what's been read is read AGAIN
+ feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
+ feedEntrySource.afterPropertiesSet();
+ entry1 = feedEntrySource.receive().getPayload();
+ entry2 = feedEntrySource.receive().getPayload();
+ entry3 = feedEntrySource.receive().getPayload();
+ assertNull(feedEntrySource.receive()); // only 3 entries in the test feed
+
+ assertEquals("Spring Integration download", entry1.getTitle().trim());
+ assertEquals(1266088337000L, entry1.getPublishedDate().getTime());
+
+ assertEquals("Check out Spring Integration forums", entry2.getTitle().trim());
+ assertEquals(1268469501000L, entry2.getPublishedDate().getTime());
+
+ assertEquals("Spring Integration adapters", entry3.getTitle().trim());
+ assertEquals(1272044098000L, entry3.getPublishedDate().getTime());
+ }
+}
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/TestFeedEventDelivery.java b/spring-integration-feed/src/test/java/org/springframework/integration/feed/TestFeedEventDelivery.java
deleted file mode 100644
index d5c0e88cb6..0000000000
--- a/spring-integration-feed/src/test/java/org/springframework/integration/feed/TestFeedEventDelivery.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.springframework.integration.feed;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
-@ContextConfiguration
-@RunWith(SpringJUnit4ClassRunner.class)
-public class TestFeedEventDelivery {
-
- @Test
- public void testDeliveryOfFeed() throws Exception {
- Thread.sleep(1000 * 60);
- }
-
- /* public static void main(String[] args) throws Throwable {
- String siweb = "http://twitter.com/statuses/public_timeline.atom"; //http://localhost:8080/siweb/foo.atom";
- FeedEntryReaderMessageSource feedEntryReaderMessageSource = new FeedEntryReaderMessageSource();
- feedEntryReaderMessageSource.setFeedUrl(siweb);
- feedEntryReaderMessageSource.afterPropertiesSet();
- feedEntryReaderMessageSource.start();
-
- while (true) {
- Message entryMessage = feedEntryReaderMessageSource.receive();
-
- if (entryMessage != null) {
- SyndEntry entry = entryMessage.getPayload();
- System.out.println((entry.getTitle() + "=" + entry.getUri()));
- }
-
- Thread.sleep(1000);
- }
- }
- */
-}
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-context.xml b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-context.xml
new file mode 100644
index 0000000000..cf4b30660f
--- /dev/null
+++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-context.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml
new file mode 100644
index 0000000000..701caed05c
--- /dev/null
+++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml
new file mode 100644
index 0000000000..34bc76beb8
--- /dev/null
+++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-http-context.xml b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-http-context.xml
new file mode 100644
index 0000000000..3e1597b65a
--- /dev/null
+++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests-http-context.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests.java b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests.java
new file mode 100644
index 0000000000..79a5a37f0e
--- /dev/null
+++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/FeedMessageSourceBeanDefinitionParserTests.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2002-2010 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.feed.config;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.File;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.integration.Message;
+import org.springframework.integration.MessagingException;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.context.metadata.MetadataStore;
+import org.springframework.integration.core.MessageHandler;
+import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
+import org.springframework.integration.feed.FeedEntryReaderMessageSource;
+import org.springframework.integration.feed.FeedReaderMessageSource;
+import org.springframework.integration.feed.FileUrlFeedFetcher;
+import org.springframework.integration.history.MessageHistory;
+import org.springframework.integration.test.util.TestUtils;
+
+import com.sun.syndication.feed.synd.SyndEntry;
+import com.sun.syndication.fetcher.impl.AbstractFeedFetcher;
+import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
+
+/**
+ * @author Oleg Zhurakousky
+ *
+ */
+public class FeedMessageSourceBeanDefinitionParserTests {
+ private static CountDownLatch latch;
+ @Before
+ public void prepare(){
+ File persisterFile = new File(System.getProperty("java.io.tmpdir") + "/spring-integration/", "feedAdapter.last.entry");
+ if (persisterFile.exists()){
+ persisterFile.delete();
+ }
+ }
+
+ @Test
+ public void validateSuccessfullConfigurationWithCustomMetastore(){
+ ClassPathXmlApplicationContext context =
+ new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-context.xml", this.getClass());
+ SourcePollingChannelAdapter adapter = context.getBean("feedAdapter", SourcePollingChannelAdapter.class);
+ FeedEntryReaderMessageSource source = (FeedEntryReaderMessageSource) TestUtils.getPropertyValue(adapter, "source");
+ MetadataStore metaStore = (MetadataStore) TestUtils.getPropertyValue(source, "metadataStore");
+ assertTrue(metaStore instanceof SampleMetadataStore);
+ FeedReaderMessageSource feedReaderMessageSource = (FeedReaderMessageSource) TestUtils.getPropertyValue(source, "feedReaderMessageSource");
+ AbstractFeedFetcher fetcher = (AbstractFeedFetcher) TestUtils.getPropertyValue(feedReaderMessageSource, "fetcher");
+ assertTrue(fetcher instanceof FileUrlFeedFetcher);
+
+ context =
+ new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-http-context.xml", this.getClass());
+ adapter = context.getBean("feedAdapter", SourcePollingChannelAdapter.class);
+ source = (FeedEntryReaderMessageSource) TestUtils.getPropertyValue(adapter, "source");
+ feedReaderMessageSource = (FeedReaderMessageSource) TestUtils.getPropertyValue(source, "feedReaderMessageSource");
+ fetcher = (AbstractFeedFetcher) TestUtils.getPropertyValue(feedReaderMessageSource, "fetcher");
+ assertTrue(fetcher instanceof HttpURLFeedFetcher);
+ context.destroy();
+ }
+
+ @Test
+ public void validateSuccessfullNewsRetrievalWithFileUrlAndMessageHistory() throws Exception{
+ File persisterFile = new File(System.getProperty("java.io.tmpdir") + "/spring-integration/", "feedAdapterUsage.last.entry");
+ if (persisterFile.exists()){
+ persisterFile.delete();
+ }
+ //Test file samples.rss has 3 news items
+ latch = spy(new CountDownLatch(3));
+ ClassPathXmlApplicationContext context =
+ new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml", this.getClass());
+ latch.await(5, TimeUnit.SECONDS);
+ verify(latch, times(3)).countDown();
+ context.destroy();
+
+ // since we are not deleting the persister file
+ // in this iteration no new feeds will be received and the latch will timeout
+ latch = spy(new CountDownLatch(3));
+ context =
+ new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml", this.getClass());
+ latch.await(5, TimeUnit.SECONDS);
+ verify(latch, times(0)).countDown();
+ context.destroy();
+ }
+ @Test
+ public void validateSuccessfullNewsRetrievalWithFileUrlNoPersistentIdentifier() throws Exception{
+ //Test file samples.rss has 3 news items
+ latch = spy(new CountDownLatch(3));
+ ClassPathXmlApplicationContext context =
+ new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml", this.getClass());
+ latch.await(5, TimeUnit.SECONDS);
+ verify(latch, times(3)).countDown();
+ context.destroy();
+
+ // since we are not deleting the persister file
+ // in this iteration no new feeds will be received and the latch will timeout
+ latch = spy(new CountDownLatch(3));
+ context =
+ new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml", this.getClass());
+ latch.await(5, TimeUnit.SECONDS);
+ verify(latch, times(3)).countDown();
+ context.destroy();
+ }
+
+ @Test
+ @Ignore // goes against the real feed
+ public void validateSuccessfullNewsRetrievalWithHttpUrl() throws Exception{
+ final CountDownLatch latch = new CountDownLatch(3);
+ MessageHandler handler = spy(new MessageHandler() {
+ public void handleMessage(Message> message) throws MessagingException {
+ latch.countDown();
+ }
+ });
+ ApplicationContext context =
+ new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-http-context.xml", this.getClass());
+ DirectChannel feedChannel = context.getBean("feedChannel", DirectChannel.class);
+ feedChannel.subscribe(handler);
+ latch.await(5, TimeUnit.SECONDS);
+ verify(handler, atLeast(3)).handleMessage(Mockito.any(Message.class));
+ }
+
+ public static class SampleService{
+ public void receiveFeedEntry(Message> message){
+ MessageHistory history = MessageHistory.read(message);
+ assertTrue(history.size() == 3);
+ Properties historyItem = history.get(0);
+ assertEquals("feedAdapterUsage", historyItem.get("name"));
+ assertEquals("feed:inbound-channel-adapter", historyItem.get("type"));
+
+ historyItem = history.get(1);
+ assertEquals("feedChannelUsage", historyItem.get("name"));
+ assertEquals("channel", historyItem.get("type"));
+
+ historyItem = history.get(2);
+ assertEquals("sampleActivator", historyItem.get("name"));
+ assertEquals("service-activator", historyItem.get("type"));
+ latch.countDown();
+ }
+ }
+
+ public static class SampleServiceNoHistory{
+ public void receiveFeedEntry(SyndEntry entry){
+ latch.countDown();
+ }
+ }
+
+ public static class SampleMetadataStore implements MetadataStore{
+
+ public void write(Properties metadata) {
+ }
+
+ public Properties load() {
+ return new Properties();
+ }
+ }
+}
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/sample.rss b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/sample.rss
new file mode 100644
index 0000000000..31fa532a39
--- /dev/null
+++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/config/sample.rss
@@ -0,0 +1,53 @@
+
+
+Spring Integration
+http://www.springsource.org/spring-integration
+
+Spring Integration is a really cool framework
+
+en-us
+Copyright 2004-2010 SpringSource/VMWare
+All Rights Reserved.
+Tue, 12 Apr 2010 18:21:32 EST
+240
+
+http://www.springsource.org/sites/all/themes/dotorg09/images/dotorg09_logo.png
+Spring Integration
+http://www.springsource.org/spring-integration
+
+
+
+
+Spring Integration adapters
+
+http://www.springsource.org/extensions/se-sia
+
+Spring Integration adapters are realy cool
+
+Tue, 23 Apr 2010 12:34:58 EST
+
+
+
+
+Spring Integration download
+
+http://www.springsource.com/products/spring-community-download
+
+Download Spring Integration
+
+Sun, 13 Feb 2010 14:12:17 EST
+
+
+
+
+Check out Spring Integration forums
+
+http://forum.springsource.org/forumdisplay.php?f=42
+
+Spring Integration forums are awesome
+
+Wed, 13 Mar 2010 03:38:21 EST
+
+
+
+
diff --git a/spring-integration-feed/src/test/java/org/springframework/integration/feed/sample.rss b/spring-integration-feed/src/test/java/org/springframework/integration/feed/sample.rss
new file mode 100644
index 0000000000..31fa532a39
--- /dev/null
+++ b/spring-integration-feed/src/test/java/org/springframework/integration/feed/sample.rss
@@ -0,0 +1,53 @@
+
+
+Spring Integration
+http://www.springsource.org/spring-integration
+
+Spring Integration is a really cool framework
+
+en-us
+Copyright 2004-2010 SpringSource/VMWare
+All Rights Reserved.
+Tue, 12 Apr 2010 18:21:32 EST
+240
+
+http://www.springsource.org/sites/all/themes/dotorg09/images/dotorg09_logo.png
+Spring Integration
+http://www.springsource.org/spring-integration
+
+
+
+
+Spring Integration adapters
+
+http://www.springsource.org/extensions/se-sia
+
+Spring Integration adapters are realy cool
+
+Tue, 23 Apr 2010 12:34:58 EST
+
+
+
+
+Spring Integration download
+
+http://www.springsource.com/products/spring-community-download
+
+Download Spring Integration
+
+Sun, 13 Feb 2010 14:12:17 EST
+
+
+
+
+Check out Spring Integration forums
+
+http://forum.springsource.org/forumdisplay.php?f=42
+
+Spring Integration forums are awesome
+
+Wed, 13 Mar 2010 03:38:21 EST
+
+
+
+
diff --git a/spring-integration-feed/src/test/resources/org/springframework/integration/feed/FeedDeliveryEventServiceActivator.java b/spring-integration-feed/src/test/resources/org/springframework/integration/feed/FeedDeliveryEventServiceActivator.java
deleted file mode 100644
index 14a2d21ade..0000000000
--- a/spring-integration-feed/src/test/resources/org/springframework/integration/feed/FeedDeliveryEventServiceActivator.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.springframework.integration.feed;
-
-import com.sun.syndication.feed.synd.SyndEntry;
-import org.apache.commons.lang.builder.ToStringBuilder;
-import org.springframework.integration.Message;
-import org.springframework.integration.annotation.ServiceActivator;
-import org.springframework.stereotype.Component;
-
-@Component
-public class FeedDeliveryEventServiceActivator {
-
- @ServiceActivator
- public void activate(Message evtMsg) throws Exception {
-
- SyndEntry syndEntry = evtMsg.getPayload();
-
- System.out.println( "Publishing new SyndEntry " + syndEntry.getUri() +":"+
- syndEntry.getPublishedDate().toString()+ ":"+ syndEntry.getPublishedDate().getTime());
-
-// System.out.println( syndEntry.toString());
- // System.out.println("Delivery! " + ToStringBuilder.reflectionToString(evtMsg));
-
- }
-
-}
diff --git a/spring-integration-feed/src/test/resources/org/springframework/integration/feed/TestFeedEventDelivery-context.xml b/spring-integration-feed/src/test/resources/org/springframework/integration/feed/TestFeedEventDelivery-context.xml
deleted file mode 100644
index 13bde9ecb6..0000000000
--- a/spring-integration-feed/src/test/resources/org/springframework/integration/feed/TestFeedEventDelivery-context.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/spring-integration-feed/template.mf b/spring-integration-feed/template.mf
index a66402d629..bb97ba3b6a 100644
--- a/spring-integration-feed/template.mf
+++ b/spring-integration-feed/template.mf
@@ -12,8 +12,6 @@ Import-Template:
org.springframework.context;version="[3.0.3, 4.0.0)",
org.springframework.core.*;version="[3.0.3, 4.0.0)",
org.springframework.util;version="[3.0.3, 4.0.0)",
- com.sun.syndication.feed.synd.*;version="[1.0.0, 2.0.0)",
- com.sun.syndication.fetcher.*;version="[1.0.0, 2.0.0)",
- com.sun.syndication.fetcher.impl.*;version="[1.0.0, 2.0.0)",
+ com.sun.syndication.*;version="[1.0.0, 2.0.0)",
javax.*;version="0",
org.w3c.dom.*;version="0"
diff --git a/spring-integration-file/input/FileMessageHistoryTest.txt b/spring-integration-file/input/FileMessageHistoryTest.txt
deleted file mode 100644
index b6fc4c620b..0000000000
--- a/spring-integration-file/input/FileMessageHistoryTest.txt
+++ /dev/null
@@ -1 +0,0 @@
-hello
\ No newline at end of file
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileListFilterFactoryBean.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileListFilterFactoryBean.java
index 21f4ec9a58..e0a0178462 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileListFilterFactoryBean.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileListFilterFactoryBean.java
@@ -16,13 +16,11 @@
package org.springframework.integration.file.config;
import org.springframework.beans.factory.FactoryBean;
-
import org.springframework.integration.file.entries.*;
+import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import java.io.File;
-
import java.util.Collection;
-import java.util.regex.Pattern;
/**
@@ -32,7 +30,7 @@ import java.util.regex.Pattern;
public class FileListFilterFactoryBean implements FactoryBean> {
private volatile EntryListFilter fileListFilter;
private volatile EntryListFilter filterReference;
- private volatile Pattern filenamePattern;
+ private volatile String filenamePattern;
private volatile Boolean preventDuplicates;
private final Object monitor = new Object();
private volatile Collection> filterReferences;
@@ -46,7 +44,7 @@ public class FileListFilterFactoryBean implements FactoryBean patternFilter = new PatternMatchingEntryListFilter(fileNamer, this.filenamePattern);
+ SimplePatternFileListFilter patternFilter = new SimplePatternFileListFilter(this.filenamePattern);
if (Boolean.FALSE.equals(this.preventDuplicates)) {
flf = patternFilter;
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileReadingMessageSourceFactoryBean.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileReadingMessageSourceFactoryBean.java
index 00932172ad..7a57605ac3 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileReadingMessageSourceFactoryBean.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileReadingMessageSourceFactoryBean.java
@@ -17,9 +17,7 @@ package org.springframework.integration.file.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.factory.FactoryBean;
-
import org.springframework.integration.file.DirectoryScanner;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
@@ -27,9 +25,6 @@ import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.locking.AbstractFileLockerFilter;
import java.io.File;
-
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Comparator;
@@ -143,8 +138,8 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean fileCompositeEntryListFilter = new CompositeEntryListFilter();
- for (EntryListFilter filter : Arrays.asList(this.filter, this.locker))
- fileCompositeEntryListFilter.addFilter(filter);
+ fileCompositeEntryListFilter.addFilter(this.filter);
+ fileCompositeEntryListFilter.addFilter(this.locker);
this.source.setFilter(fileCompositeEntryListFilter);
this.source.setLocker(locker);
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AbstractEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AbstractEntryListFilter.java
index 7de94a91cb..6925a29b7a 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AbstractEntryListFilter.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AbstractEntryListFilter.java
@@ -33,6 +33,9 @@ import java.util.List;
public abstract class AbstractEntryListFilter implements InitializingBean, EntryListFilter {
public abstract boolean accept(T t);
+ /**
+ * {@inheritDoc}
+ */
public List filterEntries(T[] entries) {
List accepted = new ArrayList();
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java
index 0ee88f6033..fb428f967b 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java
@@ -54,8 +54,9 @@ public class CompositeEntryListFilter implements EntryListFilter {
return leftOver;
}
+ @SuppressWarnings("unchecked") //to please the eclipse compiler
public CompositeEntryListFilter addFilter(EntryListFilter filter) {
- return this.addFilters(Arrays.asList(filter));
+ return this.addFilters(filter);
}
/**
@@ -63,8 +64,7 @@ public class CompositeEntryListFilter implements EntryListFilter {
* @return this CompositeFileFilter instance with the added filters
* @see #addFilters(Collection)
*/
- @SuppressWarnings("unused")
- public CompositeEntryListFilter addFilters(EntryListFilter[] filters) {
+ public CompositeEntryListFilter addFilters(EntryListFilter... filters) {
return addFilters(Arrays.asList(filters));
}
@@ -76,8 +76,9 @@ public class CompositeEntryListFilter implements EntryListFilter {
* @param filtersToAdd a list of filters to add
* @return this CompositeEntryListFilter instance with the added filters
*/
- public CompositeEntryListFilter addFilters(Collection> filtersToAdd) {
- for (EntryListFilter elf : filtersToAdd)
+ @SuppressWarnings("unchecked")
+ public CompositeEntryListFilter addFilters(Collection extends EntryListFilter> filtersToAdd) {
+ for (EntryListFilter extends T> elf : filtersToAdd)
if (elf instanceof InitializingBean) {
try {
((InitializingBean) elf).afterPropertiesSet();
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryListFilter.java
index 20dca2c18d..58d4e0d11f 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryListFilter.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryListFilter.java
@@ -19,18 +19,23 @@ import java.util.List;
/**
- * Strategy interface for filtering a group of entries / files.
+ * Strategy interface for filtering entries representing files on a local or remote file system. This is a generic
+ * variant of FileListFilter that also works with references to remote files.
*
- * {@link EntryListFilter} that passes file entries only one time. This can
- * conveniently be used to prevent duplication of files, as is done in
- * {@link org.springframework.integration.file.FileReadingMessageSource}.
- *
- * This implementation is thread safe.
+ * Implementations must be thread safe.
*
- * @author Iwein Fuld
* @author Josh Long
- * @since 1.0.0
+ * @author Iwein Fuld
+ *
+ * @since 2.0.0
+ *
+ * @see org.springframework.integration.file.filters.FileListFilter
*/
public interface EntryListFilter {
+
+ /**
+ * Filters out entries and returns the entries that are left in a list, or an
+ * empty list when a null is passed in.
+ */
List filterEntries(T[] entries);
}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java
index fe9778f77b..df5df2c3bb 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java
@@ -23,14 +23,14 @@ import java.util.regex.Pattern;
/**
- *
- *
* Filters a listing of entries (T) by qualifying their 'name' (as determined by {@link org.springframework.integration.file.entries.EntryNamer})
* against a regular expression (an instance of {@link java.util.regex.Pattern})
*
* @author Iwein Fuld
* @author Josh Long
* @param the type of entry
+ *
+ * @since 2.0.0
*/
public class PatternMatchingEntryListFilter extends AbstractEntryListFilter implements InitializingBean {
private Pattern pattern;
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractFileListFilter.java
new file mode 100644
index 0000000000..484876148f
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractFileListFilter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2008 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.file.filters;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A convenience base class for any {@link FileListFilter} whose criteria can be
+ * evaluated against each File in isolation. If the entire List of files is
+ * required for evaluation, implement the FileListFilter interface directly.
+ *
+ * @author Mark Fisher
+ * @author Iwein Fuld
+ *
+ * @deprecated Replaced by AbstractEntryListFilter in 2.0.0
+ */
+@Deprecated
+public abstract class AbstractFileListFilter implements FileListFilter {
+
+ /**
+ * {@inheritDoc}
+ */
+ public final List filterFiles(File[] files) {
+ List accepted = new ArrayList();
+ if (files != null) {
+ for (File file : files) {
+ if (this.accept(file)) {
+ accepted.add(file);
+ }
+ }
+ }
+ return accepted;
+ }
+
+ /**
+ * Subclasses must implement this method.
+ */
+ protected abstract boolean accept(File file);
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AcceptOnceFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AcceptOnceFileListFilter.java
new file mode 100644
index 0000000000..85cd327fae
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AcceptOnceFileListFilter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2010 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.file.filters;
+
+import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
+import org.springframework.util.Assert;
+
+import java.io.File;
+import java.util.List;
+
+
+/**
+ * {@link FileListFilter} that passes files only one time. This can
+ * conveniently be used to prevent duplication of files, as is done in
+ * {@link org.springframework.integration.file.FileReadingMessageSource}.
+ *
+ * This implementation is thread safe.
+ *
+ * @author Iwein Fuld
+ * @since 1.0.0
+ */
+public class AcceptOnceFileListFilter extends AcceptOnceEntryFileListFilter implements FileListFilter{
+
+ /**
+ * Creates an AcceptOnceFileFilter that is based on a bounded queue. If the
+ * queue overflows, files that fall out will be passed through this filter
+ * again if passed to the {@link #filterFiles(File[])} method.
+ *
+ * @param maxCapacity the maximum number of Files to maintain in the 'seen'
+ * queue.
+ */
+ public AcceptOnceFileListFilter(int maxCapacity) {
+ super(maxCapacity);
+ }
+
+ /**
+ * Creates an AcceptOnceFileFilter based on an unbounded queue.
+ */
+ public AcceptOnceFileListFilter() {
+ super();
+ }
+
+ /**
+ * Filter out all the files that this instance has seen before.
+ */
+ public List filterFiles(File[] files) {
+ Assert.notNull(files, "'files' must not be null.");
+ return this.filterEntries(files);
+ }
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/CompositeFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/CompositeFileListFilter.java
new file mode 100644
index 0000000000..416a1f7272
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/CompositeFileListFilter.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2009 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.file.filters;
+
+import org.springframework.integration.file.entries.CompositeEntryListFilter;
+import org.springframework.integration.file.entries.EntryListFilter;
+import org.springframework.util.Assert;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.*;
+
+
+/**
+ * Composition that delegates to multiple {@link FileFilter}s. The composition is AND based, meaning that a file must
+ * pass through each filter's {@link #filterFiles(java.io.File[])} method in order to be accepted by the composite.
+ *
+ * @author Iwein Fuld
+ * @author Mark Fisher
+ */
+public class CompositeFileListFilter extends CompositeEntryListFilter implements FileListFilter{
+
+ public CompositeFileListFilter(EntryListFilter... fileFilters) {
+ this(Arrays.asList(fileFilters));
+ }
+
+ public CompositeFileListFilter(Collection extends EntryListFilter> fileFilters) {
+ super(fileFilters);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * This implementation delegates to a collection of filters and returns only files that pass all the filters.
+ */
+ public List filterFiles(File[] files) {
+ Assert.notNull(files, "'files' should not be null.");
+ return this.filterEntries(files);
+ }
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileListFilter.java
new file mode 100644
index 0000000000..65529f4eba
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileListFilter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2008 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.file.filters;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Strategy interface for filtering a group of files.
+ *
+ * @author Iwein Fuld
+ *
+ * @since 1.0.0
+ *
+ * @see org.springframework.integration.file.entries.EntryListFilter
+ *
+ */
+public interface FileListFilter {
+
+ /**
+ * Filters out files and returns the files that are left in a list, or an
+ * empty list when a null is passed in.
+ */
+ List filterFiles(File[] files);
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/PatternMatchingFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/PatternMatchingFileListFilter.java
new file mode 100644
index 0000000000..797df701b3
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/PatternMatchingFileListFilter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2008 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.file.filters;
+
+import org.springframework.integration.file.entries.FileEntryNamer;
+import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
+import org.springframework.util.Assert;
+
+import java.io.File;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * An {@link org.springframework.integration.file.entries.EntryListFilter} implementation that matches a File against a {@link Pattern}.
+ *
+ * @author Iwein Fuld
+ * @author Mark Fisher
+ *
+ * @since 1.0.0
+ */
+public class PatternMatchingFileListFilter extends PatternMatchingEntryListFilter implements FileListFilter{
+
+ /**
+ * Create a file filter for the given pattern.
+ */
+ public PatternMatchingFileListFilter(Pattern pattern) {
+ super(new FileEntryNamer(), pattern);
+ }
+
+ public PatternMatchingFileListFilter(String pattern) {
+ super(new FileEntryNamer(), pattern);
+ }
+
+ /**
+ * Filter out the files of which the name doesn't match the pattern of this filter
+ */
+ public List filterFiles(File[] files) {
+ Assert.notNull(files, "'files' must not be null.");
+ return this.filterEntries(files);
+ }
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/SimplePatternFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/SimplePatternFileListFilter.java
new file mode 100644
index 0000000000..46fc59aec1
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/SimplePatternFileListFilter.java
@@ -0,0 +1,40 @@
+package org.springframework.integration.file.filters;
+
+import org.springframework.integration.file.entries.AbstractEntryListFilter;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Filter that supports ant style path expressions, which are less powerful but more readable than regular expressions.
+ * This filter only filters on the name of the file, the rest of the path is ignored.
+ *
+ * @author Iwein Fuld
+ * @see org.springframework.util.AntPathMatcher
+ * @see org.springframework.integration.file.filters.PatternMatchingFileListFilter
+ * @since 2.0.0
+ */
+public class SimplePatternFileListFilter extends AbstractEntryListFilter implements FileListFilter {
+
+ private final AntPathMatcher matcher = new AntPathMatcher();
+ private final String path;
+
+ public SimplePatternFileListFilter(String path) {
+ this.path = path;
+ }
+
+ /**
+ * Accept the given file its name matches the pattern,
+ */
+ @Override
+ public boolean accept(File file) {
+ return matcher.match(path, file.getName());
+ }
+
+ public List filterFiles(File[] files) {
+ Assert.notNull("'files' must not be null.");
+ return this.filterEntries(files);
+ }
+}
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests-context.xml
index f0336e1452..3a5b040b70 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests-context.xml
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests-context.xml
@@ -13,22 +13,20 @@
-
-
+
@@ -36,7 +34,7 @@
-
+
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileToChannelIntegrationTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/FileToChannelIntegrationTests-context.xml
index 0462c2fb0f..2f22357250 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileToChannelIntegrationTests-context.xml
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileToChannelIntegrationTests-context.xml
@@ -33,7 +33,7 @@
-
+
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
index c90c945ba7..dcdca5b78d 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
@@ -54,6 +54,9 @@ public class FileWritingMessageHandlerTests {
super.create();
outputDirectory = temp.newFolder("outputDirectory");
handler = new FileWritingMessageHandler(outputDirectory);
+ sourceFile = temp.newFile("sourceFile");
+ FileCopyUtils.copy(SAMPLE_CONTENT.getBytes(DEFAULT_ENCODING),
+ new FileOutputStream(sourceFile, false));
}
};
@@ -63,12 +66,7 @@ public class FileWritingMessageHandlerTests {
@Before
public void setup() throws Exception {
- sourceFile = File.createTempFile("tempSourceFileForTests", ".txt");
- sourceFile.deleteOnExit();
- FileCopyUtils.copy(SAMPLE_CONTENT.getBytes(DEFAULT_ENCODING),
- new FileOutputStream(sourceFile, false));
- outputDirectory = temp.newFolder("outputDirectory");
- handler = new FileWritingMessageHandler(outputDirectory);
+ //don't tamper with temp files here, Rule is applied later
}
@Test(expected = MessageHandlingException.class)
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests-context.xml
index bfcb0e3ce9..168e6c93ed 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests-context.xml
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests-context.xml
@@ -18,15 +18,13 @@
-
+
-
-
+
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests.java
index cd5cc874df..caedf9aa4d 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests.java
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests.java
@@ -21,7 +21,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.file.DefaultDirectoryScanner;
@@ -48,12 +47,7 @@ public class FileInboundChannelAdapterParserTests {
private ApplicationContext context;
@Autowired
- // @Qualifier("inputDirPoller")
private FileReadingMessageSource source;
-
-// @Autowired
-// @Qualifier("inputDirPollerWithChannel")
-// private FileReadingMessageSource sourceWithChannel;
private DirectFieldAccessor accessor;
@@ -62,7 +56,6 @@ public class FileInboundChannelAdapterParserTests {
accessor = new DirectFieldAccessor(source);
}
-
@Test
public void channelName() throws Exception {
Object adapter = context.getBean("inputDirPoller");
@@ -102,6 +95,5 @@ public class FileInboundChannelAdapterParserTests {
public int compare(File f1, File f2) {
return 0;
}
- }
-
+ }
}
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests-context.xml
index 24f536d111..4a60aa8bdd 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests-context.xml
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests-context.xml
@@ -14,7 +14,7 @@
+ filename-pattern="*.txt" auto-startup="false">
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests.java
index 7fb76e2f19..f8af5f78b2 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests.java
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests.java
@@ -28,13 +28,12 @@ import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
-import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
+import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.File;
import java.util.Set;
-import java.util.regex.Pattern;
import static org.junit.Assert.*;
@@ -120,16 +119,14 @@ public class FileInboundChannelAdapterWithPatternParserTests {
Set filters = (Set) new DirectFieldAccessor(
scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters");
- Pattern pattern = null;
+ String pattern = null;
for (EntryListFilter filter : filters) {
- if (filter instanceof PatternMatchingEntryListFilter) {
- pattern = (Pattern) new DirectFieldAccessor(filter).getPropertyValue("pattern");
+ if (filter instanceof SimplePatternFileListFilter) {
+ pattern = (String) new DirectFieldAccessor(filter).getPropertyValue("path");
}
}
- assertNotNull("expected PatternMatchingFileListFilter", pattern);
- assertEquals(".*\\.txt", pattern.toString());
- assertFalse(pattern.matcher("foo").matches());
- assertTrue(pattern.matcher("foo.txt").matches());
+ assertNotNull("expected SimplePatternFileListFilterTest", pattern);
+ assertEquals("*.txt", pattern.toString());
}
}
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests.java
index 55b12232e9..6a68e7cd00 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests.java
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests.java
@@ -15,33 +15,28 @@
*/
package org.springframework.integration.file.config;
-import static org.junit.Assert.*;
-
import org.junit.Test;
-
import org.junit.runner.RunWith;
-
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
-
import org.springframework.context.ApplicationContext;
-
import org.springframework.integration.file.TestFileListFilter;
import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
-import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
-
+import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.File;
-
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+
/**
* @author Mark Fisher
*/
@@ -88,7 +83,7 @@ public class FileInboundChannelAdapterWithPreventDuplicatesFlagTests {
Collection filters = (Collection) new DirectFieldAccessor(filter).getPropertyValue("fileFilters");
Iterator> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceEntryFileListFilter);
- assertTrue(iterator.next() instanceof PatternMatchingEntryListFilter);
+ assertThat(iterator.next(), is(SimplePatternFileListFilter.class));
}
@Test
@@ -100,14 +95,14 @@ public class FileInboundChannelAdapterWithPreventDuplicatesFlagTests {
Collection filters = (Collection) new DirectFieldAccessor(filter).getPropertyValue("fileFilters");
Iterator iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceEntryFileListFilter);
- assertTrue(iterator.next() instanceof PatternMatchingEntryListFilter);
+ assertThat(iterator.next(), is(SimplePatternFileListFilter.class));
}
@Test
public void patternAndFalse() throws Exception {
EntryListFilter filter = this.extractFilter("patternAndFalse");
assertFalse(filter instanceof CompositeEntryListFilter);
- assertTrue(filter instanceof PatternMatchingEntryListFilter);
+ assertThat(filter, is(SimplePatternFileListFilter.class));
}
@Test
@@ -152,7 +147,12 @@ public class FileInboundChannelAdapterWithPreventDuplicatesFlagTests {
@SuppressWarnings("unchecked")
private EntryListFilter extractFilter(String beanName) {
- return (EntryListFilter) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(context.getBean(beanName)).getPropertyValue("source")).getPropertyValue("scanner")).getPropertyValue(
- "filter");
+ return (EntryListFilter)
+ new DirectFieldAccessor(
+ new DirectFieldAccessor(
+ new DirectFieldAccessor(context.getBean(beanName))
+ .getPropertyValue("source"))
+ .getPropertyValue("scanner"))
+ .getPropertyValue("filter");
}
}
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileListFilterFactoryBeanTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileListFilterFactoryBeanTests.java
index a12083bc45..582d05e9c0 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileListFilterFactoryBeanTests.java
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileListFilterFactoryBeanTests.java
@@ -19,16 +19,18 @@ package org.springframework.integration.file.config;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.integration.file.entries.*;
+import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import java.io.File;
import java.util.Collection;
import java.util.Iterator;
-import java.util.regex.Pattern;
+import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
/**
* @author Mark Fisher
+ * @author Iwein Fuld
*/
public class FileListFilterFactoryBeanTests {
@@ -36,7 +38,7 @@ public class FileListFilterFactoryBeanTests {
public void customFilterAndFilenamePatternAreMutuallyExclusive() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilterReference(new TestFilter());
- factory.setFilenamePattern(Pattern.compile("foo"));
+ factory.setFilenamePattern("foo");
factory.getObject();
}
@@ -79,37 +81,37 @@ public class FileListFilterFactoryBeanTests {
@SuppressWarnings("unchecked")
public void filenamePatternAndPreventDuplicatesNull() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
- factory.setFilenamePattern(Pattern.compile("foo"));
+ factory.setFilenamePattern("foo");
EntryListFilter result = factory.getObject();
assertTrue(result instanceof CompositeEntryListFilter);
Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters");
Iterator iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceEntryFileListFilter);
- assertTrue(iterator.next() instanceof PatternMatchingEntryListFilter);
+ assertThat(iterator.next(), is(SimplePatternFileListFilter.class));
}
@Test
@SuppressWarnings("unchecked")
public void filenamePatternAndPreventDuplicatesTrue() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
- factory.setFilenamePattern(Pattern.compile("foo"));
+ factory.setFilenamePattern(("foo"));
factory.setPreventDuplicates(Boolean.TRUE);
EntryListFilter result = factory.getObject();
assertTrue(result instanceof CompositeEntryListFilter);
Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters");
Iterator iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceEntryFileListFilter);
- assertTrue(iterator.next() instanceof PatternMatchingEntryListFilter);
+ assertThat(iterator.next(), is(SimplePatternFileListFilter.class));
}
@Test
public void filenamePatternAndPreventDuplicatesFalse() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
- factory.setFilenamePattern(Pattern.compile("foo"));
+ factory.setFilenamePattern(("foo"));
factory.setPreventDuplicates(Boolean.FALSE);
EntryListFilter result = factory.getObject();
assertFalse(result instanceof CompositeEntryListFilter);
- assertTrue(result instanceof PatternMatchingEntryListFilter);
+ assertThat(result, is(SimplePatternFileListFilter.class));
}
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileMessageHistoryTest.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileMessageHistoryTest.java
index a0ee919cb4..004ff3a002 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileMessageHistoryTest.java
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileMessageHistoryTest.java
@@ -15,15 +15,8 @@
*/
package org.springframework.integration.file.config;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.util.Properties;
-
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
@@ -31,23 +24,39 @@ import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.test.util.TestUtils;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.util.Properties;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
/**
* @author Oleg Zhurakousky
+ * @author Iwein Fuld
*
*/
public class FileMessageHistoryTest {
+
+
@Test
public void testMessageHistory() throws Exception{
ApplicationContext context = new ClassPathXmlApplicationContext("file-message-history-context.xml", this.getClass());
- File file = new File("input/FileMessageHistoryTest.txt");
+ TemporaryFolder input = context.getBean(TemporaryFolder.class);
+ File file = input.newFile("FileMessageHistoryTest.txt");
BufferedWriter out = new BufferedWriter(new FileWriter(file));
out.write("hello");
out.close();
PollableChannel outChannel = context.getBean("outChannel", PollableChannel.class);
Message> message = outChannel.receive(1000);
+ assertThat(message, is(notNullValue()));
MessageHistory history = MessageHistory.read(message);
- assertNotNull(history);
+ assertThat(history, is(notNullValue()));
Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "fileAdapter", 0);
assertNotNull(componentHistoryRecord);
assertEquals("file:inbound-channel-adapter", componentHistoryRecord.get("type"));
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/file-message-history-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/file-message-history-context.xml
index 0b5a606023..6bdaf15410 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/file-message-history-context.xml
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/file-message-history-context.xml
@@ -9,8 +9,10 @@
-
-
+
+
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/filters/SimplePatternFileListFilterTest.java b/spring-integration-file/src/test/java/org/springframework/integration/file/filters/SimplePatternFileListFilterTest.java
new file mode 100644
index 0000000000..4d34937005
--- /dev/null
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/filters/SimplePatternFileListFilterTest.java
@@ -0,0 +1,32 @@
+package org.springframework.integration.file.filters;
+
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Iwein Fuld
+ *
+ * Minimal test set to ensure AntPathMatcher is used correctly.
+ */
+public class SimplePatternFileListFilterTest {
+
+ @Test
+ public void shouldMatchExactly() {
+ assertThat(new SimplePatternFileListFilter("bar").accept(new File("bar")), is(true));
+ }
+
+ @Test
+ public void shouldMatchQuestionMark() {
+ assertThat(new SimplePatternFileListFilter("*bar").accept(new File("bar")), is(true));
+ }
+
+ @Test
+ public void shouldMatchWildcard() {
+ assertThat(new SimplePatternFileListFilter("ba?").accept(new File("bar")), is(true));
+ }
+
+}
diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/recursive/FileInboundChannelAdapterWithRecursiveDirectoryTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/recursive/FileInboundChannelAdapterWithRecursiveDirectoryTests.java
index 680d8fdba3..b8792d06fd 100644
--- a/spring-integration-file/src/test/java/org/springframework/integration/file/recursive/FileInboundChannelAdapterWithRecursiveDirectoryTests.java
+++ b/spring-integration-file/src/test/java/org/springframework/integration/file/recursive/FileInboundChannelAdapterWithRecursiveDirectoryTests.java
@@ -31,6 +31,7 @@ import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.junit.matchers.JUnitMatchers.hasItems;
import static org.springframework.integration.test.matcher.PayloadMatcher.hasPayload;
@@ -53,24 +54,24 @@ public class FileInboundChannelAdapterWithRecursiveDirectoryTests {
//when
File folder = directory.newFolder("foo");
File file = new File(folder, "bar");
- file.createNewFile();
+ assertTrue(file.createNewFile());
//verify
assertThat(files.receive(), hasPayload(file));
}
+ @SuppressWarnings("unchecked")
@Test(timeout = 2000)
- @SuppressWarnings("unchecked")
- public void shouldReturnFilesMultipleLevels() throws IOException {
+ public void shouldReturnFilesMultipleLevels() throws IOException {
- //when
- File folder = directory.newFolder("foo");
- File siblingFile = directory.newFile("bar");
- File childFile = new File(folder, "baz");
- childFile.createNewFile();
+ //when
+ File folder = directory.newFolder("foo");
+ File siblingFile = directory.newFile("bar");
+ File childFile = new File(folder, "baz");
+ assertTrue(childFile.createNewFile());
- List> received = Arrays.asList(files.receive(), files.receive());
- //verify
- assertThat(received, hasItems(hasPayload(siblingFile), hasPayload(childFile)));
- }
+ List> received = Arrays.asList(files.receive(), files.receive());
+ //verify
+ assertThat(received, hasItems(hasPayload(siblingFile), hasPayload(childFile)));
+ }
}
diff --git a/spring-integration-ftp/.project b/spring-integration-ftp/.project
index 53e5185f7c..78f8f6bd2d 100644
--- a/spring-integration-ftp/.project
+++ b/spring-integration-ftp/.project
@@ -15,8 +15,14 @@
+
+ org.springframework.ide.eclipse.core.springbuilder
+
+
+
+ org.springframework.ide.eclipse.core.springnatureorg.maven.ide.eclipse.maven2Natureorg.eclipse.jdt.core.javanature
diff --git a/spring-integration-ftp/pom.xml b/spring-integration-ftp/pom.xml
index 1e205193b1..fa15545c9e 100644
--- a/spring-integration-ftp/pom.xml
+++ b/spring-integration-ftp/pom.xml
@@ -70,6 +70,12 @@
${project.version}compile
+
+ org.springframework.integration
+ spring-integration-test
+ ${project.version}
+ test
+ commons-langcommons-lang
diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandler.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandler.java
index d3311c3143..036aad90e0 100644
--- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandler.java
+++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandler.java
@@ -27,14 +27,13 @@ import java.nio.charset.Charset;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.net.ftp.FTPClient;
-import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.integration.Message;
import org.springframework.integration.MessageDeliveryException;
-import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
+import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
@@ -44,12 +43,12 @@ import org.springframework.util.FileCopyUtils;
* @author Iwein Fuld
* @author Mark Fisher
* @author Josh Long
+ * @author Oleg Zhurakousky
*/
-public class FtpSendingMessageHandler implements MessageHandler, InitializingBean {
+public class FtpSendingMessageHandler extends AbstractMessageHandler{
private static final String TEMPORARY_FILE_SUFFIX = ".writing";
-
private volatile FtpClientPool ftpClientPool;
private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
@@ -85,7 +84,7 @@ public class FtpSendingMessageHandler implements MessageHandler, InitializingBea
this.charset = charset;
}
- public void afterPropertiesSet() throws Exception {
+ protected void onInit() throws Exception {
Assert.notNull(ftpClientPool, "'ftpClientPool' must not be null");
Assert.notNull(temporaryBufferFolder,
"'temporaryBufferFolder' must not be null");
@@ -143,13 +142,29 @@ public class FtpSendingMessageHandler implements MessageHandler, InitializingBea
}
}
- /* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */
+ private boolean sendFile(File file, FTPClient client) throws FileNotFoundException, IOException {
+ FileInputStream fileInputStream = new FileInputStream(file);
+ boolean sent = client.storeFile(file.getName(), fileInputStream);
+ fileInputStream.close();
+ return sent;
+ }
- public void handleMessage(Message> message) {
+ private FTPClient getFtpClient() throws SocketException, IOException {
+ FTPClient client;
+ client = this.ftpClientPool.getClient();
+ Assert.state(client != null, FtpClientPool.class.getSimpleName() +
+ " returned 'null' client this most likely a bug in the pool implementation.");
+ return client;
+ }
+
+ @Override
+ protected void handleMessageInternal(Message> message) throws Exception {
Assert.notNull(message, "'message' must not be null");
Object payload = message.getPayload();
Assert.notNull(payload, "Message payload must not be null");
+
File file = this.redeemForStorableFile(message);
+
if ((file != null) && file.exists()) {
FTPClient client = null;
boolean sentSuccesfully;
@@ -190,19 +205,4 @@ public class FtpSendingMessageHandler implements MessageHandler, InitializingBea
}
}
- private boolean sendFile(File file, FTPClient client) throws FileNotFoundException, IOException {
- FileInputStream fileInputStream = new FileInputStream(file);
- boolean sent = client.storeFile(file.getName(), fileInputStream);
- fileInputStream.close();
- return sent;
- }
-
- private FTPClient getFtpClient() throws SocketException, IOException {
- FTPClient client;
- client = this.ftpClientPool.getClient();
- Assert.state(client != null, FtpClientPool.class.getSimpleName() +
- " returned 'null' client this most likely a bug in the pool implementation.");
- return client;
- }
-
}
diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java
index 9e1476ddec..61ddb5ca39 100644
--- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java
+++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java
@@ -8,6 +8,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
+import org.springframework.integration.file.FileNameGenerator;
/**
@@ -27,11 +28,17 @@ public class FtpSendingMessageHandlerFactoryBean extends AbstractFactoryBean
-
-
+
+
+
+
+ Allows you to specify a reference to
+ [org.springframework.integration.file.FileNameGenerator] implementation.
+
+
+
+
+
+
+
+
+
+
+
+ Allows you to specify a reference to
+ [org.springframework.integration.file.FileNameGenerator] implementation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserOutboundTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserOutboundTests.java
new file mode 100644
index 0000000000..4a59d2e96a
--- /dev/null
+++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/FtpParserOutboundTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2010 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.ftp;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.integration.Message;
+import org.springframework.integration.endpoint.EventDrivenConsumer;
+import org.springframework.integration.file.FileNameGenerator;
+import org.springframework.integration.message.GenericMessage;
+import org.springframework.integration.test.util.TestUtils;
+
+/**
+ * @author Oleg Zhurakousky
+ *
+ */
+public class FtpParserOutboundTests {
+
+
+ @Test
+ public void testFtpOutboundWithFileGenerator() throws Exception{
+ ClassPathXmlApplicationContext context =
+ new ClassPathXmlApplicationContext("FtpParserOutboundTests-context.xml", this.getClass());
+
+ FileNameGenerator fileNameGenerator = context.getBean("fileNameGenerator", FileNameGenerator.class);
+ assertNotNull(fileNameGenerator);
+ when(fileNameGenerator.generateFileName(Mockito.any(Message.class))).thenReturn("oleg-ftp-test.txt");
+
+ EventDrivenConsumer fileOutboundEndpoint = context.getBean("ftpOutboundAdapter", EventDrivenConsumer.class);
+ FtpSendingMessageHandler handler = (FtpSendingMessageHandler) TestUtils.getPropertyValue(fileOutboundEndpoint, "handler");
+ Message message = new GenericMessage("ftp file generator test");
+ try {
+ handler.handleMessage(message);
+ } catch (Exception e) {
+ // ignore
+ }
+ verify(fileNameGenerator, times(1)).generateFileName(message);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-integration-ftp/src/test/resources/inbound-ftp-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound-ftp-context.xml
similarity index 100%
rename from spring-integration-ftp/src/test/resources/inbound-ftp-context.xml
rename to spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound-ftp-context.xml
diff --git a/spring-integration-ftp/src/test/resources/inbound-ftps-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound-ftps-context.xml
similarity index 100%
rename from spring-integration-ftp/src/test/resources/inbound-ftps-context.xml
rename to spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound-ftps-context.xml
diff --git a/spring-integration-ftp/src/test/resources/outbound-ftp-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound-ftp-context.xml
similarity index 100%
rename from spring-integration-ftp/src/test/resources/outbound-ftp-context.xml
rename to spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound-ftp-context.xml
diff --git a/spring-integration-ftp/src/test/resources/outbound-ftps-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound-ftps-context.xml
similarity index 100%
rename from spring-integration-ftp/src/test/resources/outbound-ftps-context.xml
rename to spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound-ftps-context.xml
diff --git a/spring-integration-ip/pom.xml b/spring-integration-ip/pom.xml
index d27670166f..5b182044d9 100644
--- a/spring-integration-ip/pom.xml
+++ b/spring-integration-ip/pom.xml
@@ -25,10 +25,6 @@
spring-integration-streamruntime
-
- org.springframework.commons
- spring-commons-serializer
- cglib
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/TcpInboundGateway.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/TcpInboundGateway.java
index 8561c978d0..fcf7c1a57e 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/TcpInboundGateway.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/TcpInboundGateway.java
@@ -46,10 +46,16 @@ public class TcpInboundGateway extends MessagingGatewaySupport implements TcpLis
public boolean onMessage(Message> message) {
Message> reply = this.sendAndReceiveMessage(message);
+ if (reply == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("null reply received for " + message + " nothing to send");
+ }
+ return false;
+ }
String connectionId = (String) message.getHeaders().get(IpHeaders.CONNECTION_ID);
TcpConnection connection = connections.get(connectionId);
if (connection == null) {
- logger.error("Connection " + connectionId + " not found");
+ logger.error("Connection " + connectionId + " not found when processing reply for " + message);
return false;
}
try {
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java
index d66727da1a..627243abb2 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractConnectionFactory.java
@@ -24,9 +24,9 @@ import java.util.concurrent.Executors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.commons.serializer.Deserializer;
-import org.springframework.commons.serializer.Serializer;
import org.springframework.context.SmartLifecycle;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer;
import org.springframework.util.Assert;
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractTcpConnection.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractTcpConnection.java
index f9b2580dcb..10496fe9e0 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractTcpConnection.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractTcpConnection.java
@@ -21,8 +21,8 @@ import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.commons.serializer.Deserializer;
-import org.springframework.commons.serializer.Serializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
import org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer;
import org.springframework.util.Assert;
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractTcpConnectionInterceptor.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractTcpConnectionInterceptor.java
index 81ef26ac74..bbbadc186a 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractTcpConnectionInterceptor.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/AbstractTcpConnectionInterceptor.java
@@ -16,8 +16,8 @@
package org.springframework.integration.ip.tcp.connection;
-import org.springframework.commons.serializer.Deserializer;
-import org.springframework.commons.serializer.Serializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
import org.springframework.integration.Message;
/**
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnection.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnection.java
index f2ee28ade8..3fbfe27708 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnection.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnection.java
@@ -19,8 +19,8 @@ package org.springframework.integration.ip.tcp.connection;
import java.net.Socket;
import java.nio.channels.SocketChannel;
-import org.springframework.commons.serializer.Deserializer;
-import org.springframework.commons.serializer.Serializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
import org.springframework.integration.Message;
/**
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionInterceptorFactory.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionInterceptorFactory.java
index c580d73f83..a256e1dad8 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionInterceptorFactory.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpConnectionInterceptorFactory.java
@@ -17,15 +17,20 @@ package org.springframework.integration.ip.tcp.connection;
/**
- * Base class for TcpConnectionInterceptorFactories. Subclasses create prototype beans by
- * default.
+ * Interface for TCP connection interceptor factories.
*
* @author Gary Russell
* @since 2.0
*
*/
-public abstract class TcpConnectionInterceptorFactory {
+public interface TcpConnectionInterceptorFactory {
+ /**
+ * Called for each new connection - if an interceptor is
+ * stateful, a new interceptor must be returned on each call.
+ *
+ * @return the TcpInterceptor
+ */
public abstract TcpConnectionInterceptor getInterceptor();
}
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetConnection.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetConnection.java
index c33deea6c3..4336771670 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetConnection.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNetConnection.java
@@ -19,7 +19,7 @@ package org.springframework.integration.ip.tcp.connection;
import java.net.Socket;
import java.net.SocketTimeoutException;
-import org.springframework.commons.serializer.Deserializer;
+import org.springframework.core.serializer.Deserializer;
import org.springframework.integration.Message;
import org.springframework.integration.ip.tcp.SocketIoUtils;
import org.springframework.integration.ip.tcp.serializer.SoftEndOfStreamException;
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/serializer/AbstractByteArraySerializer.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/serializer/AbstractByteArraySerializer.java
index b94e3e00f5..749acb6bcd 100644
--- a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/serializer/AbstractByteArraySerializer.java
+++ b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/serializer/AbstractByteArraySerializer.java
@@ -20,9 +20,8 @@ import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
-import org.springframework.commons.serializer.Deserializer;
-import org.springframework.commons.serializer.Serializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
/**
* Base class for (de)serializers that provide a mechanism to
diff --git a/spring-integration-ip/src/main/resources/org/springframework/integration/ip/config/spring-integration-ip-2.0.xsd b/spring-integration-ip/src/main/resources/org/springframework/integration/ip/config/spring-integration-ip-2.0.xsd
index f1a8098a8b..12dd334a58 100644
--- a/spring-integration-ip/src/main/resources/org/springframework/integration/ip/config/spring-integration-ip-2.0.xsd
+++ b/spring-integration-ip/src/main/resources/org/springframework/integration/ip/config/spring-integration-ip-2.0.xsd
@@ -294,7 +294,7 @@ the factory, the connection will be closed after a response is received.
-
+
@@ -308,7 +308,7 @@ would normally be the same but this is not required.
-
+
diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml b/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml
index f665a16205..4882381548 100644
--- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml
+++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml
@@ -214,9 +214,9 @@
-
+
-
+
-
+
-
+
diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/SharedConnectionTests-context.xml b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/SharedConnectionTests-context.xml
index 81646ba7bd..742a3d9349 100644
--- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/SharedConnectionTests-context.xml
+++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/SharedConnectionTests-context.xml
@@ -11,9 +11,9 @@
-
+
-
+
-
-
+
+ org.springframework.integration
spring-integration-core
-
- org.springframework.commons
- spring-commons-serializer
- cglib
diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageStore.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageStore.java
index 6ef9206afe..71b4577817 100644
--- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageStore.java
+++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcMessageStore.java
@@ -18,19 +18,22 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.commons.serializer.Deserializer;
-import org.springframework.commons.serializer.DeserializingConverter;
-import org.springframework.commons.serializer.Serializer;
-import org.springframework.commons.serializer.SerializingConverter;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
+import org.springframework.core.serializer.support.DeserializingConverter;
+import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.integration.Message;
import org.springframework.integration.store.AbstractMessageGroupStore;
import org.springframework.integration.store.MessageGroup;
@@ -41,6 +44,7 @@ import org.springframework.integration.util.UUIDConverter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
+import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
@@ -72,11 +76,7 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
private static final String CREATE_MESSAGE = "INSERT into %PREFIX%MESSAGE(MESSAGE_ID, REGION, CREATED_DATE, MESSAGE_BYTES)"
+ " values (?, ?, ?, ?)";
- private static final String LIST_UNMARKED_MESSAGES_BY_GROUP_KEY = "SELECT MESSAGE_ID, CREATED_DATE, GROUP_KEY, MESSAGE_BYTES from %PREFIX%MESSAGE_GROUP where GROUP_KEY=? and REGION=? and MARKED=0 order by CREATED_DATE";
-
- private static final String LIST_MARKED_MESSAGES_BY_GROUP_KEY = "SELECT MESSAGE_ID, CREATED_DATE, GROUP_KEY, MESSAGE_BYTES from %PREFIX%MESSAGE_GROUP where GROUP_KEY=? and REGION=? and MARKED=1";
-
- private static final String GET_MIN_CREATED_DATE_BY_GROUP_KEY = "SELECT MIN(CREATED_DATE) from %PREFIX%MESSAGE_GROUP where GROUP_KEY=? and REGION=?";
+ private static final String LIST_MESSAGES_BY_GROUP_KEY = "SELECT MESSAGE_ID, CREATED_DATE, GROUP_KEY, MESSAGE_BYTES, MARKED from %PREFIX%MESSAGE_GROUP where GROUP_KEY=? and REGION=? order by CREATED_DATE";
private static final String MARK_MESSAGES_IN_GROUP = "UPDATE %PREFIX%MESSAGE_GROUP set UPDATED_DATE=?, MARKED=1 where MARKED=0 and GROUP_KEY=? and REGION=?";
@@ -109,7 +109,7 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
private String tablePrefix = DEFAULT_TABLE_PREFIX;
private JdbcOperations jdbcTemplate;
-
+
private DeserializingConverter deserializer;
private SerializingConverter serializer;
@@ -195,25 +195,25 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
public void setLobHandler(LobHandler lobHandler) {
this.lobHandler = lobHandler;
}
-
+
/**
* A converter for serializing messages to byte arrays for storage.
*
* @param serializer the serializer to set
*/
@SuppressWarnings("unchecked")
- public void setSerializer(Serializer/* super Message>>*/ serializer) {
- this.serializer = new SerializingConverter(serializer);
+ public void setSerializer(Serializer super Message>> serializer) {
+ this.serializer = new SerializingConverter((Serializer) serializer);
}
-
+
/**
* A converter for deserializing byte arrays to messages.
*
* @param deserializer the deserializer to set
*/
- @SuppressWarnings("unchecked")
- public void setDeserializer(Deserializer/* super Message>>*/ deserializer) {
- this.deserializer = new DeserializingConverter(deserializer);
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void setDeserializer(Deserializer extends Message>> deserializer) {
+ this.deserializer = new DeserializingConverter((Deserializer) deserializer);
}
/**
@@ -258,8 +258,8 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
}
final long createdDate = System.currentTimeMillis();
- Message result = MessageBuilder.fromMessage(message).setHeader(SAVED_KEY, Boolean.TRUE).setHeader(
- CREATED_DATE_KEY, new Long(createdDate)).build();
+ Message result = MessageBuilder.fromMessage(message).setHeader(SAVED_KEY, Boolean.TRUE)
+ .setHeader(CREATED_DATE_KEY, new Long(createdDate)).build();
final String messageId = getKey(result.getHeaders().getId());
final byte[] messageBytes = serializer.convert(result);
@@ -292,24 +292,35 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
lobHandler.getLobCreator().setBlobAsBytes(ps, 5, messageBytes);
}
});
-
+
return getMessageGroup(groupId);
}
public MessageGroup getMessageGroup(Object groupId) {
String key = getKey(groupId);
- List> marked = jdbcTemplate.query(getQuery(LIST_MARKED_MESSAGES_BY_GROUP_KEY), new Object[] {
- key, region }, mapper);
- List> unmarked = jdbcTemplate.query(getQuery(LIST_UNMARKED_MESSAGES_BY_GROUP_KEY),
- new Object[] { key, region }, mapper);
+ final List> marked = new ArrayList>();
+ final List> unmarked = new ArrayList>();
+ final AtomicReference date = new AtomicReference();
+ jdbcTemplate.query(getQuery(LIST_MESSAGES_BY_GROUP_KEY), new Object[] { key, region },
+ new RowCallbackHandler() {
+ int count = 0;
+ public void processRow(ResultSet rs) throws SQLException {
+ int markedFlag = rs.getInt("MARKED");
+ Message> message = mapper.mapRow(rs, count++);
+ if (markedFlag > 0) {
+ marked.add(message);
+ } else {
+ unmarked.add(message);
+ }
+ date.set(rs.getTimestamp("CREATED_DATE"));
+ }
+ });
if (marked.isEmpty() && unmarked.isEmpty()) {
return new SimpleMessageGroup(groupId);
}
- Timestamp date = jdbcTemplate.queryForObject(getQuery(GET_MIN_CREATED_DATE_BY_GROUP_KEY),
- Timestamp.class, key, region);
- Assert.state(date != null, "Could not locate created date for groupId=" + groupId);
- long timestamp = date.getTime();
+ Assert.state(date.get() != null, "Could not locate created date for groupId=" + groupId);
+ long timestamp = date.get().getTime();
return new SimpleMessageGroup(unmarked, marked, groupId, timestamp);
}
@@ -357,7 +368,7 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
jdbcTemplate.update(getQuery(MARK_MESSAGE_IN_GROUP), new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
- logger.debug("Marking message "+messageId+" in group with group key=" + groupKey);
+ logger.debug("Marking message " + messageId + " in group with group key=" + groupKey);
ps.setTimestamp(1, new Timestamp(updatedDate));
ps.setString(2, messageId);
ps.setString(3, groupKey);
@@ -384,9 +395,8 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
@Override
public Iterator iterator() {
- @SuppressWarnings("unchecked")
final Iterator iterator = jdbcTemplate.query(getQuery(LIST_GROUP_KEYS), new Object[] { region },
- new SingleColumnRowMapper(String.class)).iterator();
+ new SingleColumnRowMapper()).iterator();
return new Iterator() {
@@ -419,8 +429,7 @@ public class JdbcMessageStore extends AbstractMessageGroupStore implements Messa
private class MessageMapper implements RowMapper> {
public Message> mapRow(ResultSet rs, int rowNum) throws SQLException {
- Message> message = (Message>) deserializer.convert(lobHandler.getBlobAsBytes(rs,
- "MESSAGE_BYTES"));
+ Message> message = (Message>) deserializer.convert(lobHandler.getBlobAsBytes(rs, "MESSAGE_BYTES"));
return message;
}
}
diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.0.xsd b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.0.xsd
index 6c64b9b50d..fe9991fbd0 100644
--- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.0.xsd
+++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.0.xsd
@@ -97,7 +97,7 @@
]]>
-
+
@@ -109,7 +109,7 @@
]]>
-
+
@@ -193,6 +193,18 @@
+
+
+
+
+ Flag to say that the poller should start automatically on startup (default true).
+
+
+
+
+
+
+
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelIntegrationTests-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelIntegrationTests-context.xml
new file mode 100644
index 0000000000..7465176f06
--- /dev/null
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelIntegrationTests-context.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelIntegrationTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelIntegrationTests.java
new file mode 100644
index 0000000000..ab03811957
--- /dev/null
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelIntegrationTests.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2002-2010 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.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+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.channel.QueueChannel;
+import org.springframework.integration.message.GenericMessage;
+import org.springframework.integration.store.MessageGroup;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+import org.springframework.util.StopWatch;
+
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class JdbcMessageStoreChannelIntegrationTests {
+
+ @Autowired
+ private QueueChannel input;
+
+ @Autowired
+ @Qualifier("lock")
+ private Object storeLock;
+
+ @Autowired
+ private JdbcMessageStore messageStore;
+
+ @Autowired
+ private PlatformTransactionManager transactionManager;
+
+ @Before
+ public void clear() {
+ for (MessageGroup group : messageStore) {
+ messageStore.removeMessageGroup(group.getGroupId());
+ }
+ }
+
+ @Test
+ public void testSendAndActivate() throws Exception {
+ Service.reset(1);
+ input.send(new GenericMessage("foo"));
+ Service.await(1000);
+ assertEquals(1, Service.messages.size());
+ }
+
+ @Test
+ // @Repeat(100)
+ public void testSendAndActivateWithRollback() throws Exception {
+ Service.reset(1);
+ Service.fail = true;
+ input.send(new GenericMessage("foo"));
+ Service.await(1000);
+ assertEquals(1, Service.messages.size());
+ // After a rollback in the poller the message is still waiting to be delivered
+ // but unless we use a transactin here there is a chance that the queue will
+ // appear empty....
+ new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
+
+ public Void doInTransaction(TransactionStatus status) {
+
+ synchronized (storeLock) {
+
+ assertEquals(1, input.getQueueSize());
+ assertNotNull(input.receive(100L));
+
+ }
+ return null;
+
+ }
+ });
+ }
+
+ @Test
+ public void testTransactionalSendAndReceive() throws Exception {
+
+ Service.reset(1);
+
+ boolean result = new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
+
+ public Boolean doInTransaction(TransactionStatus status) {
+
+ synchronized (storeLock) {
+
+ boolean result = input.send(new GenericMessage("foo"), 500L);
+ // This will time out because the transaction has not committed yet
+ try {
+ Service.await(1000);
+ fail("Expected timeout");
+ } catch (Exception e) {
+ // expected
+ }
+
+ return result;
+
+ }
+
+ }
+ });
+
+ assertTrue("Could not send message", result);
+
+ // So no activation
+ assertEquals(0, Service.messages.size());
+
+ StopWatch stopWatch = new StopWatch();
+ try {
+ stopWatch.start();
+ // It might be null or not, but we don't want it to block
+ input.receive(100L);
+ } finally {
+ stopWatch.stop();
+ }
+
+ // If the poll blocks in the RDBMS there is no way for the queue to respect the timeout
+ assertTrue("Timed out waiting for receive", stopWatch.getTotalTimeMillis() < 10000);
+
+ }
+
+ @Test
+ public void testSameTransactionSendAndReceive() throws Exception {
+
+ Service.reset(1);
+ final StopWatch stopWatch = new StopWatch();
+ DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
+
+ // With a timeout on the transaction the test fails (after a long time) on the assertion in the transactional
+ // receive.
+ transactionDefinition.setTimeout(200);
+
+ boolean result = new TransactionTemplate(transactionManager, transactionDefinition)
+ .execute(new TransactionCallback() {
+
+ public Boolean doInTransaction(TransactionStatus status) {
+
+ synchronized (storeLock) {
+
+ boolean result = input.send(new GenericMessage("foo"), 500L);
+ // This will time out because the transaction has not committed yet
+ try {
+ Service.await(1000);
+ fail("Expected timeout");
+ } catch (Exception e) {
+ // expected
+ }
+
+ try {
+ stopWatch.start();
+ assertNotNull(input.receive(100L));
+ } finally {
+ stopWatch.stop();
+ }
+
+ return result;
+
+ }
+
+ }
+ });
+
+ assertTrue("Could not send message", result);
+
+ // So no activation
+ assertEquals(0, Service.messages.size());
+
+ // If the poll blocks in the RDBMS there is no way for the queue to respect the timeout
+ assertTrue("Timed out waiting for receive", stopWatch.getTotalTimeMillis() < 1000);
+
+ }
+
+ public static class Service {
+ private static boolean fail = false;
+
+ private static List messages = new CopyOnWriteArrayList();
+
+ private static CountDownLatch latch = new CountDownLatch(0);
+
+ public static void reset(int count) {
+ fail = false;
+ messages.clear();
+ latch = new CountDownLatch(count);
+ }
+
+ public static void await(long timeout) throws InterruptedException {
+ if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Timed out waiting for message");
+ }
+ }
+
+ public String echo(String input) {
+ messages.add(input);
+ latch.countDown();
+ if (fail) {
+ throw new RuntimeException("Planned failure");
+ }
+ return input;
+ }
+ }
+
+}
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelOnePollerIntegrationTests-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelOnePollerIntegrationTests-context.xml
new file mode 100644
index 0000000000..7befa295dd
--- /dev/null
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelOnePollerIntegrationTests-context.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelOnePollerIntegrationTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelOnePollerIntegrationTests.java
new file mode 100644
index 0000000000..79c702b36c
--- /dev/null
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelOnePollerIntegrationTests.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2002-2010 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.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+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.channel.QueueChannel;
+import org.springframework.integration.message.GenericMessage;
+import org.springframework.integration.store.MessageGroup;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+import org.springframework.util.StopWatch;
+
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class JdbcMessageStoreChannelOnePollerIntegrationTests {
+
+ @Autowired
+ private QueueChannel relay;
+
+ @Autowired
+ private QueueChannel durable;
+
+ @Autowired
+ @Qualifier("lock")
+ private Object storeLock;
+
+ @Autowired
+ private JdbcMessageStore messageStore;
+
+ @Autowired
+ private PlatformTransactionManager transactionManager;
+
+ @Before
+ public void clear() {
+ for (MessageGroup group : messageStore) {
+ messageStore.removeMessageGroup(group.getGroupId());
+ }
+ }
+
+ @Test
+ // @Repeat(50)
+ public void testSameTransactionDifferentChannelSendAndReceive() throws Exception {
+
+ Service.reset(1);
+ assertNull(durable.receive(100L));
+ assertNull(relay.receive(100L));
+ final StopWatch stopWatch = new StopWatch();
+
+ boolean result = new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
+
+ public Boolean doInTransaction(TransactionStatus status) {
+
+ synchronized (storeLock) {
+
+ boolean result = relay.send(new GenericMessage("foo"), 500L);
+ // This will time out because the transaction has not committed yet
+ try {
+ Service.await(1000);
+ fail("Expected timeout");
+ } catch (Exception e) {
+ // expected
+ }
+
+ try {
+ stopWatch.start();
+ // It hasn't arrive yet because we are still in the sending transaction
+ assertNull(durable.receive(100L));
+ } finally {
+ stopWatch.stop();
+ }
+
+ return result;
+
+ }
+
+ }
+ });
+
+ assertTrue("Could not send message", result);
+ // If the poll blocks in the RDBMS there is no way for the queue to respect the timeout
+ assertTrue("Timed out waiting for receive", stopWatch.getTotalTimeMillis() < 10000);
+
+ Service.await(1000);
+ // Eventual activation
+ assertEquals(1, Service.messages.size());
+
+ /*
+ * Without the storeLock:
+ *
+ * If we do this in a transaction it deadlocks occasionally. Without a transaction and it's pretty much every
+ * time.
+ *
+ * With the storeLock: It doesn't deadlock as long as the lock is injected into the poller as well.
+ */
+ new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
+
+ public Void doInTransaction(TransactionStatus status) {
+ synchronized (storeLock) {
+
+ try {
+ stopWatch.start();
+ durable.receive(100L);
+ return null;
+ } finally {
+ stopWatch.stop();
+ }
+
+ }
+ }
+
+ });
+
+ // If the poll blocks in the RDBMS there is no way for the queue to respect the timeout
+ assertTrue("Timed out waiting for receive", stopWatch.getTotalTimeMillis() < 10000);
+
+ }
+
+ public static class Service {
+ private static boolean fail = false;
+
+ private static List messages = new CopyOnWriteArrayList();
+
+ private static CountDownLatch latch = new CountDownLatch(0);
+
+ public static void reset(int count) {
+ fail = false;
+ messages.clear();
+ latch = new CountDownLatch(count);
+ }
+
+ public static void await(long timeout) throws InterruptedException {
+ if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Timed out waiting for message");
+ }
+ }
+
+ public String echo(String input) {
+ messages.add(input);
+ latch.countDown();
+ if (fail) {
+ throw new RuntimeException("Planned failure");
+ }
+ return input;
+ }
+ }
+
+}
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelTests-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelTests-context.xml
index 551e92cfb3..997a101146 100644
--- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelTests-context.xml
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreChannelTests-context.xml
@@ -32,8 +32,7 @@
-
-
+
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreTests.java
index 0aab2a4e2a..7bac021445 100644
--- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreTests.java
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/JdbcMessageStoreTests.java
@@ -39,8 +39,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.commons.serializer.Deserializer;
-import org.springframework.commons.serializer.Serializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
import org.springframework.integration.Message;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.store.MessageGroup;
@@ -89,14 +89,14 @@ public class JdbcMessageStoreTests {
@Transactional
public void testSerializer() throws Exception {
// N.B. these serializers are not realistic (just for test purposes)
- messageStore.setSerializer(new Serializer/*>>*/() {
- public void serialize(/*Message>*/ Object object, OutputStream outputStream) throws IOException {
+ messageStore.setSerializer(new Serializer>() {
+ public void serialize(Message> object, OutputStream outputStream) throws IOException {
outputStream.write(((Message>) object).getPayload().toString().getBytes());
outputStream.flush();
}
});
- messageStore.setDeserializer(new Deserializer/*>*/() {
- public Message> deserialize(InputStream inputStream) throws IOException {
+ messageStore.setDeserializer(new Deserializer>() {
+ public GenericMessage deserialize(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
return new GenericMessage(reader.readLine());
}
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/LockInterceptor.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/LockInterceptor.java
new file mode 100644
index 0000000000..5a5adfd18a
--- /dev/null
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/LockInterceptor.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2010 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 org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class LockInterceptor implements MethodInterceptor {
+
+ public synchronized Object invoke(MethodInvocation invocation) throws Throwable {
+ return invocation.proceed();
+ }
+
+}
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcMessageStoreParserTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcMessageStoreParserTests.java
index e683f87892..3f557fc9e2 100644
--- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcMessageStoreParserTests.java
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcMessageStoreParserTests.java
@@ -10,11 +10,11 @@ import java.io.OutputStream;
import org.junit.After;
import org.junit.Test;
-import org.springframework.commons.serializer.DefaultDeserializer;
-import org.springframework.commons.serializer.DefaultSerializer;
-import org.springframework.commons.serializer.Deserializer;
-import org.springframework.commons.serializer.Serializer;
import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.core.serializer.DefaultDeserializer;
+import org.springframework.core.serializer.DefaultSerializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
import org.springframework.integration.Message;
import org.springframework.integration.jdbc.JdbcMessageStore;
import org.springframework.integration.store.MessageStore;
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcPollingChannelAdapterParserTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcPollingChannelAdapterParserTests.java
index 1981f14285..c6a51dcc06 100644
--- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcPollingChannelAdapterParserTests.java
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcPollingChannelAdapterParserTests.java
@@ -57,6 +57,14 @@ public class JdbcPollingChannelAdapterParserTests {
private PlatformTransactionManager transactionManager;
+ @Test
+ public void testNoAutoStartupInboundChannelAdapter() {
+ setUp("pollingNoAutoStartupJdbcInboundChannelAdapterTest.xml", getClass());
+ this.jdbcTemplate.update("insert into item values(1,'',2)");
+ Message> message = messagingTemplate.receive();
+ assertNull("Message found ", message);
+ }
+
@Test
public void testSimpleInboundChannelAdapter() {
setUp("pollingForMapJdbcInboundChannelAdapterTest.xml", getClass());
diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/pollingNoAutoStartupJdbcInboundChannelAdapterTest.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/pollingNoAutoStartupJdbcInboundChannelAdapterTest.xml
new file mode 100644
index 0000000000..0a123f1d50
--- /dev/null
+++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/pollingNoAutoStartupJdbcInboundChannelAdapterTest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/spring-integration-jdbc/template.mf b/spring-integration-jdbc/template.mf
index 0b26fd55c4..b96f483bf5 100644
--- a/spring-integration-jdbc/template.mf
+++ b/spring-integration-jdbc/template.mf
@@ -3,9 +3,8 @@ Bundle-Name: Spring Integration JDBC Support
Bundle-Vendor: SpringSource
Bundle-ManifestVersion: 2
Import-Template:
- org.springframework.commons.*;version="[1.0.0, 2.0.0)",
org.springframework.integration.*;version="[2.0.0, 2.0.1)",
- org.springframework.*;version="[3.0.3, 4.0.0)",
+ org.springframework.*;version="[3.0.5, 4.0.0)",
org.apache.commons.logging;version="[1.1.1, 2.0.0)",
org.aopalliance.*;version="[1.0.0, 2.0.0)",
javax.sql.*;version="0",
diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/control/ControlBus.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/ControlBus.java
similarity index 95%
rename from spring-integration-jmx/src/main/java/org/springframework/integration/control/ControlBus.java
rename to spring-integration-jmx/src/main/java/org/springframework/integration/jmx/ControlBus.java
index 53e88a4311..771a62d1a2 100644
--- a/spring-integration-jmx/src/main/java/org/springframework/integration/control/ControlBus.java
+++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/ControlBus.java
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package org.springframework.integration.control;
-
-import javax.management.MBeanServer;
+package org.springframework.integration.jmx;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
@@ -26,12 +24,12 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.integration.Message;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.core.SubscribableChannel;
-import org.springframework.integration.jmx.JmxHeaders;
-import org.springframework.integration.jmx.OperationInvokingMessageHandler;
import org.springframework.integration.monitor.ObjectNameLocator;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
+import javax.management.MBeanServer;
+
/**
* JMX-based Control Bus implementation. Routes control messages on an operation channel to the other control points
* (channels and handlers) via JMX. To use the control bus send a message to the operation channel with a header
diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/ControlBusFactoryBean.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/ControlBusFactoryBean.java
index 9e7287f38f..77cb1ef046 100644
--- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/ControlBusFactoryBean.java
+++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/ControlBusFactoryBean.java
@@ -17,7 +17,7 @@ package org.springframework.integration.jmx.config;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.integration.channel.DirectChannel;
-import org.springframework.integration.control.ControlBus;
+import org.springframework.integration.jmx.ControlBus;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
index 43946ca853..104d27b29b 100644
--- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
+++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java
@@ -24,6 +24,7 @@ import java.util.concurrent.locks.ReentrantLock;
import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.aop.Advisor;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.TargetSource;
@@ -42,7 +43,6 @@ import org.springframework.context.SmartLifecycle;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.MessageHandler;
-import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
@@ -180,17 +180,12 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
}
if (bean instanceof MessageHandler) {
- SimpleMessageHandlerMetrics monitor = null;
- if (bean instanceof MessageProducer) {
- // We need to maintain semantics of the handler also being a producer
- monitor = new SimpleMessageProducingHandlerMetrics((MessageHandler) bean);
- } else {
- monitor = new SimpleMessageHandlerMetrics((MessageHandler) bean);
- }
+ SimpleMessageHandlerMetrics monitor = new SimpleMessageHandlerMetrics((MessageHandler) bean);
Object advised = applyHandlerInterceptor(bean, monitor, beanClassLoader);
handlers.add(monitor);
return advised;
- } else if (bean instanceof MessageSource>) {
+ }
+ else if (bean instanceof MessageSource>) {
SimpleMessageSourceMetrics monitor = new SimpleMessageSourceMetrics((MessageSource>) bean);
Object advised = applySourceInterceptor(bean, monitor, beanClassLoader);
sources.add(monitor);
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusOperationChannelTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusOperationChannelTests.java
index 4bafcf3187..83b2e9ec51 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusOperationChannelTests.java
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusOperationChannelTests.java
@@ -29,6 +29,7 @@ import org.springframework.integration.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
+import org.springframework.integration.jmx.ControlBus;
import org.springframework.integration.jmx.JmxHeaders;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
import org.springframework.integration.support.MessageBuilder;
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java
index ff28cdec6c..3ad903112a 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java
@@ -32,7 +32,6 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
-import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.integration.Message;
@@ -42,6 +41,7 @@ import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.BridgeHandler;
+import org.springframework.integration.jmx.ControlBus;
import org.springframework.integration.monitor.IntegrationMBeanExporter;
import org.springframework.integration.monitor.LifecycleMessageHandlerMetrics;
import org.springframework.integration.monitor.QueueChannelMetrics;
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests-context.xml
index ce73230e21..03ac2709fd 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests-context.xml
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests-context.xml
@@ -21,7 +21,7 @@
-
+
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests.java
index 71c885450f..ac22cf5c22 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests.java
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests.java
@@ -25,7 +25,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
-import org.springframework.integration.control.ControlBus;
+import org.springframework.integration.jmx.ControlBus;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MethodInvokerTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MethodInvokerTests-context.xml
new file mode 100644
index 0000000000..f4a2fed5fd
--- /dev/null
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MethodInvokerTests-context.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MethodInvokerTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MethodInvokerTests.java
new file mode 100644
index 0000000000..67df132ba0
--- /dev/null
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MethodInvokerTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2010 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.jmx.config;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.integration.Message;
+import org.springframework.integration.MessageChannel;
+import org.springframework.integration.MessagingException;
+import org.springframework.integration.core.MessageHandler;
+import org.springframework.integration.core.SubscribableChannel;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Dave Syer
+ * @since 2.0
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class MethodInvokerTests {
+
+ @Autowired
+ private MBeanServer server;
+
+ @Autowired
+ private MessageChannel echos;
+
+ @Autowired
+ private SubscribableChannel underscores;
+
+ @Test
+ public void testHandlerMBeanRegistration() throws Exception {
+ Set names = server.queryNames(new ObjectName("test.MethodInvoker:type=MessageHandler,*"), null);
+ // System.err.println(names);
+ // the router and the error handler...
+ assertEquals(2, names.size());
+ underscores.subscribe(new MessageHandler() {
+ public void handleMessage(Message> message) throws MessagingException {
+ assertEquals("foo", message.getPayload());
+ }
+ });
+ echos.send(MessageBuilder.withPayload("foo").setHeader("entity-type", "underscore").build());
+ }
+
+}
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml
index b9ae8c7926..d19f5e6bf4 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml
@@ -19,8 +19,8 @@
-
+
diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests-context.xml
index 481ec54291..27234f27a4 100644
--- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests-context.xml
+++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests-context.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/spring-integration-parent/pom.xml b/spring-integration-parent/pom.xml
index 762ac20fe2..01e742cae6 100644
--- a/spring-integration-parent/pom.xml
+++ b/spring-integration-parent/pom.xml
@@ -23,7 +23,7 @@
1.8.41.11.5.10
- 3.0.3.RELEASE
+ 3.0.5.RELEASE3.0.3.RELEASE1.5.9
@@ -136,16 +136,6 @@
spring-aspects${org.springframework.version}
-
org.springframeworkspring-core
@@ -216,11 +206,6 @@
spring-integration-stream${project.version}
-
- org.springframework.commons
- spring-commons-serializer
- 1.0.0.BUILD-SNAPSHOT
- org.springframework.securityspring-security-core
diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java
index 2770d5d3f0..3ca6ac8558 100644
--- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java
+++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java
@@ -23,7 +23,6 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
-import org.springframework.integration.sftp.impl.SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean;
import org.w3c.dom.Element;
diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java
similarity index 88%
rename from spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java
rename to spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java
index 42db16f148..804082a0ae 100644
--- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java
+++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.integration.sftp.impl;
+package org.springframework.integration.sftp.config;
import com.jcraft.jsch.ChannelSftp;
import org.apache.commons.lang.SystemUtils;
@@ -28,21 +28,20 @@ import org.springframework.integration.file.entries.PatternMatchingEntryListFilt
import org.springframework.integration.sftp.QueuedSftpSessionPool;
import org.springframework.integration.sftp.SftpEntryNamer;
import org.springframework.integration.sftp.SftpSessionFactory;
-import org.springframework.integration.sftp.config.SftpSessionUtils;
+import org.springframework.integration.sftp.impl.SftpInboundRemoteFileSystemSynchronizer;
+import org.springframework.integration.sftp.impl.SftpInboundRemoteFileSystemSynchronizingMessageSource;
import org.springframework.util.StringUtils;
import java.io.File;
/**
- * a factory bean to hide the fairly complex configuration possibilities for an SFTP endpoint
+ * Factory bean to hide the fairly complex configuration possibilities for an SFTP endpoint
*
* @author Josh Long
*/
public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends AbstractFactoryBean implements ResourceLoaderAware {
- /**
- * injected by the container
- */
+
private volatile ResourceLoader resourceLoader;
private volatile Resource localDirectoryResource;
private volatile String localDirectoryPath;
@@ -54,9 +53,9 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
private String host;
private String keyFile;
private String keyFilePassword;
- private String password;
private String remoteDirectory;
private String username;
+ private String password;
@SuppressWarnings("unused")
public void setLocalDirectoryResource(Resource localDirectoryResource) {
@@ -108,30 +107,50 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
this.keyFilePassword = keyFilePassword;
}
- @SuppressWarnings("unused")
- public void setPassword(String password) {
- this.password = password;
- }
-
+ /**
+ * Set the remote directory to synchronize with
+ */
@SuppressWarnings("unused")
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
+ /**
+ * Set the user name to be used for authentication with the remote server
+ */
@SuppressWarnings("unused")
public void setUsername(String username) {
this.username = username;
}
+ /**
+ * Set the password to be used for authentication with the remote server
+ * @param password
+ */
+ @SuppressWarnings("unused")
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public Class> getObjectType() {
return SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.class;
}
+ /**
+ * {@inheritDoc}
+ * @return Fully configured SftpInboundRemoteFileSystemSynchronizingMessageSource
+ */
@Override
protected SftpInboundRemoteFileSystemSynchronizingMessageSource createInstance()
throws Exception {
@@ -148,7 +167,7 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
this.localDirectoryPath = "file://" + sftpTmp.getAbsolutePath();
}
- this.localDirectoryResource = this.fromText(localDirectoryPath);
+ this.localDirectoryResource = this.resourceFromString(localDirectoryPath);
// remote predicates
SftpEntryNamer sftpEntryNamer = new SftpEntryNamer();
@@ -194,7 +213,7 @@ public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends A
return sftpMsgSrc;
}
- private Resource fromText(String path) {
+ private Resource resourceFromString(String path) {
ResourceEditor resourceEditor = new ResourceEditor(this.resourceLoader);
resourceEditor.setAsText(path);
diff --git a/spring-integration-twitter/.classpath b/spring-integration-twitter/.classpath
index 2daddec399..85b5f296bb 100644
--- a/spring-integration-twitter/.classpath
+++ b/spring-integration-twitter/.classpath
@@ -3,7 +3,6 @@
-
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/AbstractInboundTwitterEndpointSupport.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/AbstractInboundTwitterEndpointSupport.java
index 8159173bb4..fc4368a83c 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/AbstractInboundTwitterEndpointSupport.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/AbstractInboundTwitterEndpointSupport.java
@@ -20,12 +20,13 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.exception.ExceptionUtils;
-
import org.springframework.context.Lifecycle;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.endpoint.AbstractEndpoint;
+import org.springframework.integration.history.HistoryWritingMessagePostProcessor;
+import org.springframework.integration.history.TrackableComponent;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.twitter.oauth.OAuthConfiguration;
import org.springframework.util.Assert;
@@ -35,31 +36,44 @@ import twitter4j.ResponseList;
import twitter4j.Twitter;
/**
- * There are a lot of operations that are common to receiving the various types of messages when using the Twitter API, and this
- * class abstracts most of them for you. Implementers must take note of {@link org.springframework.integration.twitter.AbstractInboundTwitterEndpointSupport#runAsAPIRateLimitsPermit(org.springframework.integration.twitter.AbstractInboundTwitterEndpointSupport.ApiCallback)}
- * which will invoke the instance of {@link org.springframework.integration.twitter.AbstractInboundTwitterEndpointSupport.ApiCallback} when the rate-limit API
- * deems that its OK to do so. This class handles keeping tabs on that and on spacing out requests as required.
+ * There are a lot of operations that are common to receiving the various types of messages when using the
+ * Twitter API, and this class abstracts most of them for you. Implementers must take note of
+ * {@link org.springframework.integration.twitter.AbstractInboundTwitterEndpointSupport#runAsAPIRateLimitsPermit(org.springframework.integration.twitter.AbstractInboundTwitterEndpointSupport.ApiCallback)}
+ * which will invoke the instance of {@link AbstractInboundTwitterEndpointSupport.ApiCallback} when the
+ * rate-limit API deems that its OK to do so. This class handles keeping tabs on that and on spacing out requests
+ * as required.
+ *
*
- * Simialarly, this class handles keeping track on the latest inbound message its received and avoiding, where possible, redelivery of
- * common messages. This functionality is enabled using the {@link org.springframework.integration.context.metadata.MetadataPersister} implementation
+ * Simialarly, this class handles keeping track on the latest inbound message its received and avoiding, where
+ * possible, redelivery of common messages. This functionality is enabled using the
+ * {@link org.springframework.integration.context.metadata.MetadataStore} implementation
*
* @author Josh Long
* @since 2.0
*/
-public abstract class AbstractInboundTwitterEndpointSupport extends AbstractEndpoint implements Lifecycle {
- protected volatile OAuthConfiguration configuration;
- protected final MessagingTemplate messagingTemplate = new MessagingTemplate();
- private volatile MessageChannel requestChannel;
- protected volatile long markerId = -1;
- protected Twitter twitter;
- private final Object markerGuard = new Object();
- private final Object apiPermitGuard = new Object();
+public abstract class AbstractInboundTwitterEndpointSupport extends AbstractEndpoint implements Lifecycle, TrackableComponent {
+
+ protected volatile OAuthConfiguration configuration;
+
+ protected final MessagingTemplate messagingTemplate = new MessagingTemplate();
+
+ private volatile MessageChannel requestChannel;
+
+ protected volatile long markerId = -1;
+
+ protected Twitter twitter;
+
+ private final Object markerGuard = new Object();
+
+ private final Object apiPermitGuard = new Object();
+
+ private final HistoryWritingMessagePostProcessor historyWritingPostProcessor = new HistoryWritingMessagePostProcessor();
- @SuppressWarnings("unused")
public void setConfiguration(OAuthConfiguration configuration) {
this.configuration = configuration;
}
+
abstract protected void markLastStatusId(T statusId);
abstract protected List sort( List rl);
@@ -77,10 +91,20 @@ public abstract class AbstractInboundTwitterEndpointSupport extends AbstractE
public long getMarkerId() {
return markerId;
}
+
+ public String getComponentType() {
+ return "twitter:inbound-dm-channel-adapter";
+ }
+
+ public void setRequestChannel(MessageChannel requestChannel) {
+ this.messagingTemplate.setDefaultChannel(requestChannel);
+ this.requestChannel = requestChannel;
+ }
@Override
protected void doStart() {
try {
+ this.historyWritingPostProcessor.setTrackableComponent(this);
refresh();
} catch (Exception e) {
throw new RuntimeException(e);
@@ -90,10 +114,26 @@ public abstract class AbstractInboundTwitterEndpointSupport extends AbstractE
protected void forward(T status) {
synchronized (this.markerGuard) {
Message twtMsg = MessageBuilder.withPayload(status).build();
- messagingTemplate.send(requestChannel, twtMsg);
+ messagingTemplate.convertAndSend(requestChannel, twtMsg, this.historyWritingPostProcessor);
markLastStatusId(status);
}
}
+
+ //abstract protected List sort(List rl);
+
+ //abstract protected void markLastStatusId(T statusId);
+
+ abstract protected void refresh() throws Exception;
+
+ protected void forwardAll(ResponseList tResponses) {
+ List stats = new ArrayList();
+
+ for (T t : tResponses)
+ stats.add(t);
+
+ for (T twitterResponse : sort(stats))
+ forward(twitterResponse);
+ }
@SuppressWarnings("unchecked")
protected void runAsAPIRateLimitsPermit(ApiCallback cb)
@@ -129,8 +169,9 @@ public abstract class AbstractInboundTwitterEndpointSupport extends AbstractE
int secondsUntilWeCanPullAgain = secondsUntilReset / remainingHits;
long msUntilWeCanPullAgain = secondsUntilWeCanPullAgain * 1000;
- logger.debug("need to Thread.sleep() " + secondsUntilWeCanPullAgain + " seconds until the next timeline pull. Have " + remainingHits +
- " remaining pull this rate period. The period ends in " + secondsUntilReset);
+ logger.debug("need to Thread.sleep() " + secondsUntilWeCanPullAgain +
+ " seconds until the next timeline pull. Have " + remainingHits +
+ " remaining pull this rate period. The period ends in " + secondsUntilReset);
Thread.sleep(msUntilWeCanPullAgain);
} catch (Throwable throwable) {
@@ -148,8 +189,6 @@ public abstract class AbstractInboundTwitterEndpointSupport extends AbstractE
return markerId > -1;
}
- abstract protected void refresh() throws Exception;
-
@Override
protected void onInit() throws Exception {
messagingTemplate.afterPropertiesSet();
@@ -162,12 +201,6 @@ public abstract class AbstractInboundTwitterEndpointSupport extends AbstractE
protected void doStop() {
}
- @SuppressWarnings("unused")
- public void setRequestChannel(MessageChannel requestChannel) {
- this.messagingTemplate.setDefaultChannel(requestChannel);
- this.requestChannel = requestChannel;
- }
-
/**
* Hook for clients to run logic when the API rate limiting lets us
*
@@ -178,4 +211,8 @@ public abstract class AbstractInboundTwitterEndpointSupport extends AbstractE
public static interface ApiCallback {
void run(C t, Twitter twitter) throws Exception;
}
+
+ public void setShouldTrack(boolean shouldTrack) {
+ this.historyWritingPostProcessor.setShouldTrack(shouldTrack);
+ }
}
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/AbstractOutboundTwitterEndpointSupport.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/AbstractOutboundTwitterEndpointSupport.java
index 4b2de425ec..ebe9b2233e 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/AbstractOutboundTwitterEndpointSupport.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/AbstractOutboundTwitterEndpointSupport.java
@@ -15,10 +15,10 @@
*/
package org.springframework.integration.twitter;
-import org.springframework.integration.core.MessageHandler;
-import org.springframework.integration.endpoint.AbstractEndpoint;
+import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.twitter.oauth.OAuthConfiguration;
import org.springframework.util.Assert;
+
import twitter4j.Twitter;
@@ -27,12 +27,11 @@ import twitter4j.Twitter;
*
* @author Josh Long
*/
-public abstract class AbstractOutboundTwitterEndpointSupport extends AbstractEndpoint implements MessageHandler {
+public abstract class AbstractOutboundTwitterEndpointSupport extends AbstractMessageHandler {
protected volatile OAuthConfiguration configuration;
protected volatile Twitter twitter;
- protected volatile StatusUpdateSupport statusUpdateSupport = new StatusUpdateSupport();
+ protected final StatusUpdateOptboundMessageMapper statusUpdateSupport = new StatusUpdateOptboundMessageMapper();
- @SuppressWarnings("unused")
public void setConfiguration(OAuthConfiguration configuration) {
this.configuration = configuration;
}
@@ -44,13 +43,4 @@ public abstract class AbstractOutboundTwitterEndpointSupport extends AbstractEnd
Assert.notNull(this.twitter, "'twitter' can't be null");
}
-
- @Override
- protected void doStart() {
- }
-
- @Override
- protected void doStop() {
- }
-
}
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/OutboundDirectMessageStatusMessageHandler.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/OutboundDirectMessageStatusMessageHandler.java
index 036ba12dfe..9fbfcc100a 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/OutboundDirectMessageStatusMessageHandler.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/OutboundDirectMessageStatusMessageHandler.java
@@ -16,10 +16,8 @@
package org.springframework.integration.twitter;
import org.springframework.integration.Message;
-import org.springframework.integration.MessageDeliveryException;
-import org.springframework.integration.MessageHandlingException;
-import org.springframework.integration.MessageRejectedException;
import org.springframework.util.Assert;
+
import twitter4j.TwitterException;
@@ -31,7 +29,9 @@ import twitter4j.TwitterException;
* @see twitter4j.Twitter
*/
public class OutboundDirectMessageStatusMessageHandler extends AbstractOutboundTwitterEndpointSupport {
- public void handleMessage(Message> message) throws MessageRejectedException, MessageHandlingException, MessageDeliveryException {
+
+ @Override
+ protected void handleMessageInternal(Message> message) throws Exception {
try {
String txt = (String) message.getPayload();
Object toUser =
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/OutboundUpdatedStatusMessageHandler.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/OutboundUpdatedStatusMessageHandler.java
index 21878574ac..cd2f5a6c3d 100644
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/OutboundUpdatedStatusMessageHandler.java
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/OutboundUpdatedStatusMessageHandler.java
@@ -16,10 +16,8 @@
package org.springframework.integration.twitter;
import org.springframework.integration.Message;
-import org.springframework.integration.MessageDeliveryException;
-import org.springframework.integration.MessageHandlingException;
-import org.springframework.integration.MessageRejectedException;
import org.springframework.util.Assert;
+
import twitter4j.StatusUpdate;
@@ -30,15 +28,11 @@ import twitter4j.StatusUpdate;
* @since 2.0
*/
public class OutboundUpdatedStatusMessageHandler extends AbstractOutboundTwitterEndpointSupport {
- public void handleMessage(Message> message) throws MessageRejectedException, MessageHandlingException, MessageDeliveryException {
- try {
- StatusUpdate statusUpdate = this.statusUpdateSupport.fromMessage(message);
- Assert.notNull(statusUpdate, "couldn't send message, unable to build a StatusUpdate instance correctly");
- this.twitter.updateStatus(statusUpdate);
- } catch (Throwable e) {
- this.logger.debug(e);
- throw new RuntimeException(e);
- }
+ @Override
+ protected void handleMessageInternal(Message> message) throws Exception {
+ StatusUpdate statusUpdate = this.statusUpdateSupport.fromMessage(message);
+ Assert.notNull(statusUpdate, "couldn't send message, unable to build a StatusUpdate instance correctly");
+ this.twitter.updateStatus(statusUpdate);
}
}
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/StatusUpdateOptboundMessageMapper.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/StatusUpdateOptboundMessageMapper.java
new file mode 100644
index 0000000000..55eb51c9ce
--- /dev/null
+++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/StatusUpdateOptboundMessageMapper.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2010 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.twitter;
+
+import org.springframework.integration.Message;
+import org.springframework.integration.MessageHandlingException;
+import org.springframework.integration.mapping.OutboundMessageMapper;
+import org.springframework.integration.twitter.model.Twitter4jGeoLocationImpl;
+import org.springframework.util.StringUtils;
+import twitter4j.StatusUpdate;
+
+
+/**
+ * Convenience class that can take a seemingly disparate jumble of headers and a payload and do a best-faith attempt at vending a {@link twitter4j.StatusUpdate} instance
+ *
+ * @author Josh Long
+ * @see twitter4j.StatusUpdate
+ * @see org.springframework.integration.twitter.TwitterHeaders
+ * @since 2.0
+ */
+public class StatusUpdateOptboundMessageMapper implements OutboundMessageMapper {
+ /**
+ * convenient, interf-ace-oriented way of obtaining a reference to a {@link org.springframework.integration.twitter.model.Twitter4jGeoLocationImpl}
+ *
+ * @param lat the latitude
+ * @param lon the longitude
+ * @return a {@link org.springframework.integration.twitter.model.GeoLocation} instance
+ */
+ public org.springframework.integration.twitter.model.GeoLocation fromLatitudeLongitudePair(double lat, double lon) {
+ return new Twitter4jGeoLocationImpl(lat, lon);
+ }
+
+ /**
+ * {@link StatusUpdate} instances are used to drive status updates.
+ *
+ * @param message the inbound messages
+ * @return a {@link StatusUpdate} that's been materialized from the inbound message
+ */
+ public StatusUpdate fromMessage(Message> message) {
+ Object payload = message.getPayload();
+ StatusUpdate statusUpdate = null;
+
+
+ if (payload instanceof String) {
+ statusUpdate = new StatusUpdate((String) payload);
+
+ if (message.getHeaders()
+ .containsKey(TwitterHeaders.TWITTER_IN_REPLY_TO_STATUS_ID)) {
+ Long replyId = (Long) message.getHeaders()
+ .get(TwitterHeaders.TWITTER_IN_REPLY_TO_STATUS_ID);
+
+ if ((replyId != null) && (replyId > 0)) {
+ statusUpdate.inReplyToStatusId(replyId);
+ }
+ }
+
+ if (message.getHeaders().containsKey(TwitterHeaders.TWITTER_PLACE_ID)) {
+ String placeId = (String) message.getHeaders()
+ .get(TwitterHeaders.TWITTER_PLACE_ID);
+
+ if (StringUtils.hasText(placeId)) {
+ statusUpdate.placeId(placeId);
+ }
+ }
+
+ if (message.getHeaders().containsKey(TwitterHeaders.TWITTER_GEOLOCATION)) {
+
+ org.springframework.integration.twitter.model.GeoLocation geoLocation = (org.springframework.integration.twitter.model.GeoLocation) message.getHeaders()
+ .get(TwitterHeaders.TWITTER_GEOLOCATION);
+ twitter4j.GeoLocation gl = null;
+
+ if (geoLocation instanceof Twitter4jGeoLocationImpl) {
+ gl = ((Twitter4jGeoLocationImpl) geoLocation).getGeoLocation();
+ if (null != gl) {
+ statusUpdate.location(gl);
+ }
+ }
+ }
+
+ if (message.getHeaders()
+ .containsKey(TwitterHeaders.TWITTER_DISPLAY_COORDINATES)) {
+ Boolean displayCoords = (Boolean) message.getHeaders()
+ .get(TwitterHeaders.TWITTER_DISPLAY_COORDINATES);
+ if (displayCoords != null) {
+ statusUpdate.displayCoordinates(displayCoords);
+ }
+ }
+ } else if (payload instanceof StatusUpdate) {
+ statusUpdate = (StatusUpdate) payload;
+ } else {
+ throw new MessageHandlingException(message,
+ "Failed to create StatusUpdate from the payload of type: " + message.getPayload().getClass() +
+ " Only java.lang.String or twitter4j.StatusUpdate is currently supported");
+ }
+
+
+ if (payload instanceof StatusUpdate) {
+ statusUpdate = (StatusUpdate) payload;
+ }
+
+ return statusUpdate;
+ }
+}
diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/StatusUpdateSupport.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/StatusUpdateSupport.java
deleted file mode 100644
index 9ac1d44b66..0000000000
--- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/StatusUpdateSupport.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2010 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.twitter;
-
-import org.springframework.integration.Message;
-import org.springframework.integration.twitter.model.GeoLocation;
-import org.springframework.integration.twitter.model.Twitter4jGeoLocationImpl;
-
-import org.springframework.util.StringUtils;
-
-import twitter4j.StatusUpdate;
-
-
-/**
- * Convenience class that can take a seemingly disparate jumble of headers and a payload and do a best-faith attempt at vending a {@link twitter4j.StatusUpdate} instance
- *
- * @author Josh Long
- * @see twitter4j.StatusUpdate
- * @see org.springframework.integration.twitter.TwitterHeaders
- * @since 2.0
- */
-public class StatusUpdateSupport {
-
- /**
- * convenient, interf-ace-oriented way of obtaining a reference to a {@link org.springframework.integration.twitter.model.Twitter4jGeoLocationImpl}
- * @param lat the latitude
- * @param lon the longitude
- * @return a {@link org.springframework.integration.twitter.model.GeoLocation} instance
- */
- public GeoLocation fromLatitudeLongitudePair ( double lat, double lon){
- return new Twitter4jGeoLocationImpl(lat, lon);
- }
- /**
- * {@link StatusUpdate} instances are used to drive status updates.
- *
- * @param message the inbound messages
- * @return a {@link StatusUpdate} that's been materialized from the inbound message
- * @throws Throwable thrown if something goes wrong
- */
- public StatusUpdate fromMessage(Message> message)
- throws Throwable {
- Object payload = message.getPayload();
- StatusUpdate statusUpdate = null;
-
- if (payload instanceof String) {
- statusUpdate = new StatusUpdate((String) payload);
-
- if (message.getHeaders()
- .containsKey(TwitterHeaders.TWITTER_IN_REPLY_TO_STATUS_ID)) {
- Long replyId = (Long) message.getHeaders()
- .get(TwitterHeaders.TWITTER_IN_REPLY_TO_STATUS_ID);
-
- if ((replyId != null) && (replyId > 0)) {
- statusUpdate.inReplyToStatusId(replyId);
- }
- }
-
- if (message.getHeaders().containsKey(TwitterHeaders.TWITTER_PLACE_ID)) {
- String placeId = (String) message.getHeaders()
- .get(TwitterHeaders.TWITTER_PLACE_ID);
-
- if (StringUtils.hasText(placeId)) {
- statusUpdate.placeId(placeId);
- }
- }
-
- if (message.getHeaders().containsKey(TwitterHeaders.TWITTER_GEOLOCATION)) {
-
- GeoLocation geoLocation = (GeoLocation) message.getHeaders()
- .get(TwitterHeaders.TWITTER_GEOLOCATION);
- twitter4j.GeoLocation gl = null;
-
- if (geoLocation instanceof Twitter4jGeoLocationImpl) {
- gl = ((Twitter4jGeoLocationImpl) geoLocation).getGeoLocation();
-
- if (null != gl) {
- statusUpdate.location(gl);
- }
- }
- }
-
- if (message.getHeaders()
- .containsKey(TwitterHeaders.TWITTER_DISPLAY_COORDINATES)) {
- Boolean displayCoords = (Boolean) message.getHeaders()
- .get(TwitterHeaders.TWITTER_DISPLAY_COORDINATES);
-
- if ((displayCoords != null) &&
- displayCoords.equals(Boolean.TRUE)) {
- statusUpdate.displayCoordinates(displayCoords);
- }
- }
- }
-
- if (payload instanceof StatusUpdate) {
- statusUpdate = (StatusUpdate) payload;
- }
-
- return statusUpdate;
- }
-}
diff --git a/spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/twitter_connection_using_ns.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/SimpleTwitterTestClient-context.xml
similarity index 100%
rename from spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/twitter_connection_using_ns.xml
rename to spring-integration-twitter/src/test/java/org/springframework/integration/twitter/SimpleTwitterTestClient-context.xml
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/SimpleTwitterTestClient.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/SimpleTwitterTestClient.java
index 7e251e7df3..09684b83a6 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/SimpleTwitterTestClient.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/SimpleTwitterTestClient.java
@@ -18,7 +18,7 @@ import java.util.Collection;
*
* @author Josh Long
*/
-@ContextConfiguration(locations = "org/springframework/integration/twitter/twitter_connection_using_ns.xml")
+@ContextConfiguration
public class SimpleTwitterTestClient {
private Twitter twitter;
@Autowired
diff --git a/spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/receiving_dms_using_ns.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestRecievingUsingNamespace-context.xml
similarity index 85%
rename from spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/receiving_dms_using_ns.xml
rename to spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestRecievingUsingNamespace-context.xml
index c661db1a0a..60a5166338 100644
--- a/spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/receiving_dms_using_ns.xml
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestRecievingUsingNamespace-context.xml
@@ -27,34 +27,35 @@
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:twitter="http://www.springframework.org/schema/integration/twitter"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
+ http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool-3.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd http://www.springframework.org/schema/integration/twitter http://www.springframework.org/schema/integration/twitter/spring-integration-twitter.xsd">
-
+
+
+
-
+
-
+ consumer-secret="${twitter.oauth.consumerSecret}"/>
-
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestRecievingUsingNamespace.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestRecievingUsingNamespace.java
index 8574f42364..3ce38bb37d 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestRecievingUsingNamespace.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestRecievingUsingNamespace.java
@@ -25,9 +25,7 @@ import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
/**
* @author Josh Long
*/
-@ContextConfiguration(locations = {
- "/org/springframework/integration/twitter/receiving_dms_using_ns.xml"}
-)
+@ContextConfiguration
public class TestRecievingUsingNamespace extends AbstractJUnit4SpringContextTests {
@Autowired
private TwitterAnnouncer twitterAnnouncer;
diff --git a/spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/sending_dms_using_ns.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingDMsUsingNamespace-context.xml
similarity index 100%
rename from spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/sending_dms_using_ns.xml
rename to spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingDMsUsingNamespace-context.xml
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingDMsUsingNamespace.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingDMsUsingNamespace.java
index 898792e277..9da62af50a 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingDMsUsingNamespace.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingDMsUsingNamespace.java
@@ -33,9 +33,7 @@ import org.springframework.util.StringUtils;
/**
* @author Josh Long
*/
-@ContextConfiguration(locations = {
- "/org/springframework/integration/twitter/sending_dms_using_ns.xml"}
-)
+@ContextConfiguration
public class TestSendingDMsUsingNamespace extends AbstractJUnit4SpringContextTests {
private volatile MessagingTemplate messagingTemplate = new MessagingTemplate();
diff --git a/spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/sending_updates_using_ns.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingUpdatesUsingNamespace-context.xml
similarity index 97%
rename from spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/sending_updates_using_ns.xml
rename to spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingUpdatesUsingNamespace-context.xml
index 360e2973c6..758fdb830d 100644
--- a/spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/sending_updates_using_ns.xml
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TestSendingUpdatesUsingNamespace-context.xml
@@ -37,7 +37,7 @@
base-package="org.springframework.integration.twitter"/>
mb = MessageBuilder.withPayload("'Hello world!', from the Spring Integration outbound Twitter adapter")
+ MessageBuilder mb = MessageBuilder.withPayload("test message 1")
.setHeader(TwitterHeaders.TWITTER_IN_REPLY_TO_STATUS_ID, 21927437001L)
.setHeader(TwitterHeaders.TWITTER_GEOLOCATION,
this.statusUpdateSupport.fromLatitudeLongitudePair(-76.226823, 23.642465)) // antarctica
diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TwitterAnnouncer.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TwitterAnnouncer.java
index 15c1451e19..22ae6ff576 100644
--- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TwitterAnnouncer.java
+++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/TwitterAnnouncer.java
@@ -4,6 +4,11 @@ import org.springframework.integration.twitter.model.DirectMessage;
import org.springframework.integration.twitter.model.Status;
import org.springframework.stereotype.Component;
+<<<<<<< HEAD
+=======
+import twitter4j.DirectMessage;
+import twitter4j.Status;
+>>>>>>> 64fec64d4095a6e793739607816d63d600e1ed0c
@Component
diff --git a/spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/receiving_replies_using_ns.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/receiving_replies_using_ns.xml
similarity index 100%
rename from spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/receiving_replies_using_ns.xml
rename to spring-integration-twitter/src/test/java/org/springframework/integration/twitter/receiving_replies_using_ns.xml
diff --git a/spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/receiving_updates_using_ns.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/receiving_updates_using_ns.xml
similarity index 100%
rename from spring-integration-twitter/src/test/resources/org/springframework/integration/twitter/receiving_updates_using_ns.xml
rename to spring-integration-twitter/src/test/java/org/springframework/integration/twitter/receiving_updates_using_ns.xml
diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/DefaultSoapHeaderMapper.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/DefaultSoapHeaderMapper.java
new file mode 100644
index 0000000000..0908e0cda4
--- /dev/null
+++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/DefaultSoapHeaderMapper.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2010 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.ws;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.xml.namespace.QName;
+
+import org.springframework.integration.MessageHeaders;
+import org.springframework.integration.mapping.HeaderMapper;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.PatternMatchUtils;
+import org.springframework.ws.soap.SoapHeader;
+import org.springframework.ws.soap.SoapHeaderElement;
+import org.springframework.xml.namespace.QNameUtils;
+
+/**
+ * A {@link HeaderMapper} implementation for mapping to and from a SoapHeader.
+ * The {@link #inboundHeaderNames} and {@link #outboundHeaderNames} may be configured.
+ * They accept exact name Strings or simple patterns (e.g. "start*", "*end", or "*").
+ * By default all inbound headers will be accepted, but any outbound header that should
+ * be mapped must be configured explicitly. Note that the outbound mapping only writes
+ * String header values into attributes on the SoapHeader. For anything more advanced,
+ * one should implement the HeaderMapper interface directly.
+ *
+ * @author Mark Fisher
+ * @since 2.0
+ */
+public class DefaultSoapHeaderMapper implements HeaderMapper {
+
+ private volatile String[] outboundHeaderNames = new String[0];
+
+ private volatile String[] inboundHeaderNames = new String[] { "*" };
+
+
+ public void setOutboundHeaderNames(String[] outboundHeaderNames) {
+ this.outboundHeaderNames = (outboundHeaderNames != null) ? outboundHeaderNames : new String[0];
+ }
+
+ public void setInboundHeaderNames(String[] inboundHeaderNames) {
+ this.inboundHeaderNames = (inboundHeaderNames != null) ? inboundHeaderNames : new String[0];
+ }
+
+ public void fromHeaders(MessageHeaders headers, SoapHeader target) {
+ if (target != null && !CollectionUtils.isEmpty(headers)) {
+ for (String headerName : headers.keySet()) {
+ if (this.shouldMapOutboundHeader(headerName)) {
+ Object value = headers.get(headerName);
+ if (value instanceof String) {
+ QName qname = QNameUtils.parseQNameString(headerName);
+ target.addAttribute(qname, (String) value);
+ }
+ }
+ }
+ }
+ }
+
+ public Map toHeaders(SoapHeader source) {
+ Map headers = new HashMap();
+ if (source != null) {
+ Iterator> attributeIter = source.getAllAttributes();
+ while (attributeIter.hasNext()) {
+ Object name = attributeIter.next();
+ if (name instanceof QName) {
+ String qnameString = QNameUtils.toQualifiedName((QName) name);
+ if (this.shouldMapInboundHeader(qnameString)) {
+ String value = source.getAttributeValue((QName) name);
+ if (value != null) {
+ headers.put(qnameString, value);
+ }
+ }
+ }
+ }
+ Iterator> elementIter = source.examineAllHeaderElements();
+ while (elementIter.hasNext()) {
+ Object element = elementIter.next();
+ if (element instanceof SoapHeaderElement) {
+ QName qname = ((SoapHeaderElement) element).getName();
+ String qnameString = QNameUtils.toQualifiedName(qname);
+ if (this.shouldMapInboundHeader(qnameString)) {
+ headers.put(qnameString, element);
+ }
+ }
+ }
+ }
+ return headers;
+ }
+
+ private boolean shouldMapInboundHeader(String headerName) {
+ return matchesAny(this.inboundHeaderNames, headerName);
+ }
+
+ private boolean shouldMapOutboundHeader(String headerName) {
+ return matchesAny(this.outboundHeaderNames, headerName);
+ }
+
+ private static boolean matchesAny(String[] patterns, String candidate) {
+ if (!ObjectUtils.isEmpty(patterns) && QNameUtils.validateQName(candidate)) {
+ for (String pattern : patterns) {
+ if (PatternMatchUtils.simpleMatch(pattern, candidate)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/MarshallingWebServiceInboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/MarshallingWebServiceInboundGateway.java
index e8d95a2a42..b058662872 100644
--- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/MarshallingWebServiceInboundGateway.java
+++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/MarshallingWebServiceInboundGateway.java
@@ -43,7 +43,10 @@ public class MarshallingWebServiceInboundGateway extends AbstractMarshallingPayl
private final ReentrantLock lifecycleLock = new ReentrantLock();
private final GatewayDelegate gatewayDelegate = new GatewayDelegate();
-
+
+ private volatile int phase = 0;
+
+
/**
* Creates a new MarshallingWebServiceInboundGateway.
* The {@link Marshaller} and {@link Unmarshaller} must be injected using properties.
@@ -95,10 +98,34 @@ public class MarshallingWebServiceInboundGateway extends AbstractMarshallingPayl
this.gatewayDelegate.setTaskScheduler(taskScheduler);
}
+ public void setShouldTrack(boolean shouldTrack) {
+ this.gatewayDelegate.setShouldTrack(shouldTrack);
+ }
+
+ public String getComponentName() {
+ return this.gatewayDelegate.getComponentName();
+ }
+
+ public String getComponentType() {
+ return this.gatewayDelegate.getComponentType();
+ }
+
public void setAutoStartup(boolean autoStartup) {
this.gatewayDelegate.setAutoStartup(autoStartup);
}
+ public boolean isAutoStartup() {
+ return this.gatewayDelegate.isAutoStartup();
+ }
+
+ public void setPhase(int phase) {
+ this.phase = phase;
+ }
+
+ public int getPhase() {
+ return this.phase;
+ }
+
public void setBeanName(String beanName) {
this.gatewayDelegate.setBeanName(beanName);
}
@@ -143,7 +170,7 @@ public class MarshallingWebServiceInboundGateway extends AbstractMarshallingPayl
public void start() {
this.lifecycleLock.lock();
try {
- if (!gatewayDelegate.isRunning()) {
+ if (!this.gatewayDelegate.isRunning()) {
this.gatewayDelegate.start();
if (logger.isInfoEnabled()) {
logger.info("started " + this);
@@ -170,10 +197,6 @@ public class MarshallingWebServiceInboundGateway extends AbstractMarshallingPayl
}
}
- public boolean isAutoStartup() {
- return true;
- }
-
public void stop(Runnable callback) {
this.lifecycleLock.lock();
try {
@@ -185,29 +208,16 @@ public class MarshallingWebServiceInboundGateway extends AbstractMarshallingPayl
}
}
- public int getPhase() {
- return 0;
- }
private static class GatewayDelegate extends MessagingGatewaySupport {
public Object sendAndReceive(Object request) {
return super.sendAndReceive(request);
}
+
public String getComponentType() {
return "ws:outbound-gateway";
}
}
- public String getComponentName() {
- return this.gatewayDelegate.getComponentName();
- }
-
- public String getComponentType() {
- return this.gatewayDelegate.getComponentType();
- }
-
- public void setShouldTrack(boolean shouldTrack) {
- this.gatewayDelegate.setShouldTrack(shouldTrack);
- }
}
diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceInboundGateway.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceInboundGateway.java
index 301956f692..eb5e02a2f9 100644
--- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceInboundGateway.java
+++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/SimpleWebServiceInboundGateway.java
@@ -16,9 +16,8 @@
package org.springframework.integration.ws;
-import java.util.Iterator;
+import java.util.Map;
-import javax.xml.namespace.QName;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
@@ -30,13 +29,14 @@ import org.springframework.expression.ExpressionException;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.gateway.MessagingGatewaySupport;
+import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.MessageEndpoint;
import org.springframework.ws.soap.SoapHeader;
-import org.springframework.ws.soap.SoapHeaderElement;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.xml.transform.StringSource;
import org.springframework.xml.transform.TransformerObjectSupport;
@@ -51,11 +51,22 @@ public class SimpleWebServiceInboundGateway extends MessagingGatewaySupport impl
private volatile boolean extractPayload = true;
+ private volatile HeaderMapper headerMapper = new DefaultSoapHeaderMapper();
+
public void setExtractPayload(boolean extractPayload) {
this.extractPayload = extractPayload;
}
+ public void setHeaderMapper(HeaderMapper headerMapper) {
+ Assert.notNull(headerMapper, "headerMapper must not be null");
+ this.headerMapper = headerMapper;
+ }
+
+ public String getComponentType() {
+ return "ws:outbound-gateway";
+ }
+
public void invoke(MessageContext messageContext) throws Exception {
try {
this.doInvoke(messageContext);
@@ -83,21 +94,9 @@ public class SimpleWebServiceInboundGateway extends MessagingGatewaySupport impl
}
if (request instanceof SoapMessage) {
SoapMessage soapMessage = (SoapMessage) request;
- SoapHeader soapHeader = soapMessage.getSoapHeader();
- if (soapHeader != null) {
- Iterator> attributeIter = soapHeader.getAllAttributes();
- while (attributeIter.hasNext()) {
- QName name = (QName) attributeIter.next();
- builder.setHeader(name.toString(), soapHeader.getAttributeValue(name));
- }
- Iterator> elementIter = soapHeader.examineAllHeaderElements();
- while (elementIter.hasNext()) {
- Object element = elementIter.next();
- if (element instanceof SoapHeaderElement) {
- QName name = ((SoapHeaderElement) element).getName();
- builder.setHeader(name.toString(), element);
- }
- }
+ Map headers = this.headerMapper.toHeaders(soapMessage.getSoapHeader());
+ if (!CollectionUtils.isEmpty(headers)) {
+ builder.copyHeaders(headers);
}
}
Message> replyMessage = this.sendAndReceiveMessage(builder.build());
@@ -120,17 +119,19 @@ public class SimpleWebServiceInboundGateway extends MessagingGatewaySupport impl
+ replyPayload.getClass().getName() + "]");
}
WebServiceMessage response = messageContext.getResponse();
+ if (response instanceof SoapMessage) {
+ this.headerMapper.fromHeaders(
+ replyMessage.getHeaders(), ((SoapMessage) response).getSoapHeader());
+ }
this.transformerSupportDelegate.transformSourceToResult(responseSource, response.getPayloadResult());
}
}
- private class TransformerSupportDelegate extends TransformerObjectSupport {
+
+ private static class TransformerSupportDelegate extends TransformerObjectSupport {
void transformSourceToResult(Source source, Result result) throws TransformerException {
this.transform(source, result);
}
}
- public String getComponentType() {
- return "ws:outbound-gateway";
- }
}
diff --git a/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParser.java b/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParser.java
index 3bad423846..475a1be4a8 100644
--- a/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParser.java
+++ b/spring-integration-ws/src/main/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParser.java
@@ -13,16 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.integration.ws.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.integration.config.xml.AbstractInboundGatewayParser;
+import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author Iwein Fuld
+ * @author Mark Fisher
*/
public class WebServiceInboundGatewayParser extends AbstractInboundGatewayParser {
@@ -48,6 +51,12 @@ public class WebServiceInboundGatewayParser extends AbstractInboundGatewayParser
builder.addConstructorArgReference(unmarshallerRef);
}
}
+ String headerMapperRef = element.getAttribute("header-mapper");
+ if (StringUtils.hasText(headerMapperRef)) {
+ Assert.isTrue(!StringUtils.hasText(marshallerRef),
+ "The 'header-mapper' attribute cannot be used when a 'marshaller' is provided.");
+ builder.addPropertyReference("headerMapper", headerMapperRef);
+ }
}
}
diff --git a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-2.0.xsd b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-2.0.xsd
index 7567e4ea64..51338510df 100644
--- a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-2.0.xsd
+++ b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-2.0.xsd
@@ -248,6 +248,21 @@
+
+
+
+ Reference to a HeaderMapper<SoapHeader> implementation
+ that this gateway will use to map between Spring Integration
+ MessageHeaders and the SoapHeader. This strategy can only be
+ applied when a 'marshaller' is not being configured.
+
+
+
+
+
+
+
+
diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests-context.xml b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests-context.xml
index b1e0a9bec0..a8f01e2a78 100644
--- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests-context.xml
+++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests-context.xml
@@ -31,7 +31,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java
index 1adddcf60d..5756a00a70 100644
--- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java
+++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceInboundGatewayParserTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2010 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.integration.ws.config;
import static junit.framework.Assert.assertEquals;
@@ -23,6 +24,8 @@ import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.util.Collections;
+import java.util.Map;
import java.util.Properties;
import javax.xml.transform.Source;
@@ -30,13 +33,16 @@ import javax.xml.transform.Source;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+
import org.springframework.beans.DirectFieldAccessor;
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.MessageHeaders;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.history.MessageHistory;
+import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.ws.MarshallingWebServiceInboundGateway;
import org.springframework.integration.ws.SimpleWebServiceInboundGateway;
@@ -46,12 +52,12 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.ws.context.DefaultMessageContext;
import org.springframework.ws.context.MessageContext;
+import org.springframework.ws.soap.SoapHeader;
/**
- *
* @author Iwein Fuld
* @author Oleg Zhurakousky
- *
+ * @author Mark Fisher
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@@ -116,6 +122,7 @@ public class WebServiceInboundGatewayParserTests {
is(marshaller));
assertTrue("messaging gateway is not running", marshallingGateway.isRunning());
}
+
@Test
public void testMessageHistoryWithMarshallingGateway() throws Exception {
MessageContext context = new DefaultMessageContext(new StubMessageFactory());
@@ -130,6 +137,7 @@ public class WebServiceInboundGatewayParserTests {
assertNotNull(componentHistoryRecord);
assertEquals("ws:outbound-gateway", componentHistoryRecord.get("type"));
}
+
@Test
public void testMessageHistoryWithSimpleGateway() throws Exception {
MessageContext context = new DefaultMessageContext(new StubMessageFactory());
@@ -142,4 +150,30 @@ public class WebServiceInboundGatewayParserTests {
assertNotNull(componentHistoryRecord);
assertEquals("ws:outbound-gateway", componentHistoryRecord.get("type"));
}
+
+ @Autowired
+ private SimpleWebServiceInboundGateway headerMappingGateway;
+
+ @Autowired
+ private HeaderMapper testHeaderMapper;
+
+ @Test
+ public void testHeaderMapperReference() throws Exception {
+ DirectFieldAccessor accessor = new DirectFieldAccessor(headerMappingGateway);
+ Object headerMapper = accessor.getPropertyValue("headerMapper");
+ assertEquals(testHeaderMapper, headerMapper);
+ }
+
+
+ @SuppressWarnings("unused")
+ private static class TestHeaderMapper implements HeaderMapper {
+
+ public void fromHeaders(MessageHeaders headers, SoapHeader target) {
+ }
+
+ public Map toHeaders(SoapHeader source) {
+ return Collections.emptyMap();
+ }
+ }
+
}
diff --git a/spring-integration-ws/template.mf b/spring-integration-ws/template.mf
index f0aa5f8361..eed0b41f62 100644
--- a/spring-integration-ws/template.mf
+++ b/spring-integration-ws/template.mf
@@ -11,7 +11,7 @@ Import-Template:
org.springframework.util;version="[3.0.3, 4.0.0)",
org.springframework.oxm;version="[1.5.8.A, 3.1.0)",
org.springframework.ws.*;version="[1.5.8.A, 2.0.0)",
- org.springframework.xml.transform;version="[1.5.8.A, 2.0.0)",
+ org.springframework.xml.*;version="[1.5.8.A, 2.0.0)",
org.apache.commons.logging;version="[1.1.1, 2.0.0)",
org.w3c.dom.*;version="0",
javax.xml.*;version="0"
diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java
index 7ffa7deb52..d98e1dcdb0 100644
--- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java
+++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java
@@ -59,6 +59,7 @@ public class XmlPayloadValidatingFilterParser extends AbstractConsumerEndpointPa
}
if (schemaLocationDefined){
selectorBuilder.addConstructorArgValue(schemaLocation);
+ // it is a restriction with the default value of 'xml-schema' which corresponds to 'http://www.w3.org/2001/XMLSchema'
String schemaType = "xml-schema".equals(element.getAttribute("schema-type")) ? SCHEMA_W3C_XML : SCHEMA_RELAX_NG;;
selectorBuilder.addConstructorArgValue(schemaType);
}
diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java
index 0d0b3e9900..471f021c25 100644
--- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java
+++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java
@@ -16,9 +16,12 @@
package org.springframework.integration.xml.selector;
+import java.io.IOException;
+
import org.springframework.core.io.Resource;
import org.springframework.integration.Message;
import org.springframework.integration.MessageHandlingException;
+import org.springframework.integration.MessageRejectedException;
import org.springframework.integration.core.MessageSelector;
import org.springframework.integration.xml.AggregatedXmlMessageValidationException;
import org.springframework.integration.xml.DefaultXmlPayloadConverter;
@@ -26,6 +29,7 @@ import org.springframework.integration.xml.XmlPayloadConverter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
import org.springframework.xml.validation.XmlValidator;
import org.springframework.xml.validation.XmlValidatorFactory;
import org.xml.sax.SAXParseException;
@@ -38,18 +42,30 @@ import org.xml.sax.SAXParseException;
public class XmlValidatingMessageSelector implements MessageSelector {
private final XmlValidator xmlValidator;
+
private volatile boolean throwExceptionOnRejection;
private volatile XmlPayloadConverter converter = new DefaultXmlPayloadConverter();
-
public XmlValidatingMessageSelector(XmlValidator xmlValidator) throws Exception{
Assert.notNull(xmlValidator, "XmlValidator can not be 'null'");
this.xmlValidator = xmlValidator;
}
-
- public XmlValidatingMessageSelector(Resource schema, String schemaType) throws Exception{
+ /**
+ * Will create this selector with default {@link XmlValidator} which
+ * will be initialized with 'schema' location as {@link Resource} and 'schemaType' as
+ * either {@link XmlValidatorFactory#SCHEMA_W3C_XML} or {@link XmlValidatorFactory#SCHEMA_RELAX_NG}.
+ * If no 'schemaType' is provided it will default to {@link XmlValidatorFactory#SCHEMA_W3C_XML};
+ *
+ * @param schema
+ * @param schemaType
+ * @throws IOException
+ */
+ public XmlValidatingMessageSelector(Resource schema, String schemaType) throws IOException {
Assert.notNull(schema, "You must provide XML schema location to perform validation");
+ if (!StringUtils.hasText(schemaType)){
+ schemaType = XmlValidatorFactory.SCHEMA_W3C_XML;
+ }
this.xmlValidator = XmlValidatorFactory.createValidator(schema, schemaType);
}
@@ -63,6 +79,7 @@ public class XmlValidatingMessageSelector implements MessageSelector {
* @param converter
*/
public void setConverter(XmlPayloadConverter converter) {
+ Assert.notNull(converter, "'converter' must not be null");
this.converter = converter;
}
@@ -76,7 +93,8 @@ public class XmlValidatingMessageSelector implements MessageSelector {
}
boolean validationSuccess = ObjectUtils.isEmpty(validationExceptions);
if (!validationSuccess && throwExceptionOnRejection){
- throw new AggregatedXmlMessageValidationException(CollectionUtils.arrayToList(validationExceptions));
+ throw new MessageRejectedException(message, "Message was rejected due to XML Validation errors",
+ new AggregatedXmlMessageValidationException(CollectionUtils.arrayToList(validationExceptions)));
}
return validationSuccess;
}
diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelectorTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelectorTests.java
new file mode 100644
index 0000000000..486f297dec
--- /dev/null
+++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelectorTests.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2010 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.xml.selector;
+
+import org.junit.Test;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+import org.springframework.xml.validation.XmlValidatorFactory;
+
+/**
+ * @author Oleg Zhurakousky
+ *
+ */
+public class XmlValidatingMessageSelectorTests {
+
+ @Test
+ public void validateCreationWithSchemaAndDefaultSchemaType() throws Exception{
+ Resource resource = new ByteArrayResource("".getBytes());
+ new XmlValidatingMessageSelector(resource, null);
+ }
+
+ @Test
+ public void validateCreationWithSchemaAndProvidedSchemaType() throws Exception{
+ Resource resource = new ByteArrayResource("".getBytes());
+ new XmlValidatingMessageSelector(resource, XmlValidatorFactory.SCHEMA_W3C_XML);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void validateFailureInvalidSchemaLanguage() throws Exception{
+ Resource resource = new ByteArrayResource("".getBytes());
+ new XmlValidatingMessageSelector(resource, "foo");
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void validateFailureWhenNoSchemaResourceProvided() throws Exception{
+ new XmlValidatingMessageSelector(null, null);
+ }
+}
diff --git a/spring-integration-xmpp/.classpath b/spring-integration-xmpp/.classpath
index 2daddec399..85b5f296bb 100644
--- a/spring-integration-xmpp/.classpath
+++ b/spring-integration-xmpp/.classpath
@@ -3,7 +3,6 @@
-
diff --git a/spring-integration-xmpp/pom.xml b/spring-integration-xmpp/pom.xml
index 73b31061eb..e5a00a90b4 100644
--- a/spring-integration-xmpp/pom.xml
+++ b/spring-integration-xmpp/pom.xml
@@ -47,6 +47,11 @@
junittest
+
+ org.mockito
+ mockito-all
+ test
+ org.springframeworkspring-context-support
diff --git a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/messages/XmppMessageSendingMessageHandler.java b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/messages/XmppMessageSendingMessageHandler.java
index d385b09cca..d6917c8157 100644
--- a/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/messages/XmppMessageSendingMessageHandler.java
+++ b/spring-integration-xmpp/src/main/java/org/springframework/integration/xmpp/messages/XmppMessageSendingMessageHandler.java
@@ -20,7 +20,9 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
import org.springframework.context.Lifecycle;
+import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.xmpp.XmppHeaders;
import org.springframework.util.Assert;
@@ -29,6 +31,7 @@ import org.springframework.util.StringUtils;
/**
* @author Josh Long
* @author Mario Gray
+ * @author Oleg Zhurakousky
* @since 2.0
*/
public class XmppMessageSendingMessageHandler extends AbstractMessageHandler implements Lifecycle {
@@ -43,25 +46,22 @@ public class XmppMessageSendingMessageHandler extends AbstractMessageHandler imp
}
protected void handleMessageInternal(final org.springframework.integration.Message> message) {
+ // pre-reqs: user to send, string to send as msg body
+ String messageBody = null;
+ String destinationUser = null;
+ Object payload = message.getPayload();
+ Assert.isInstanceOf(String.class, payload, "Only payload of type String is suported. You " +
+ "can apply transformer prior to sending message to this handler");
+ messageBody = (String) payload;
+ destinationUser = (String) message.getHeaders().get(XmppHeaders.CHAT_TO_USER);
+ Assert.state(StringUtils.hasText(destinationUser), "'" + XmppHeaders.CHAT_TO_USER + "' header must not be null");
+ String threadId = (String) message.getHeaders().get(XmppHeaders.CHAT_THREAD_ID);
+ Chat chat = getOrCreateChatWithParticipant(destinationUser, threadId);
+ // TODO - figure out what to do with chat.threadId?
try {
- // pre-reqs: user to send, string to send as msg body
- String messageBody = null;
- String destinationUser = null;
- Object payload = message.getPayload();
- if (payload instanceof String) {
- messageBody = (String) payload;
- }
- destinationUser = (String) message.getHeaders().get(XmppHeaders.CHAT_TO_USER);
- Assert.state(StringUtils.hasText(destinationUser), "the destination user must not be null");
- Assert.state(StringUtils.hasText(messageBody), "the message body must not be null");
- String threadId = (String) message.getHeaders().get(XmppHeaders.CHAT_THREAD_ID);
- Chat chat = getOrCreateChatWithParticipant(destinationUser, threadId);
- if (chat != null) {
- chat.sendMessage(messageBody);
- }
- }
- catch (Exception e) {
- logger.debug("failed to send XMPP message", e);
+ chat.sendMessage(messageBody);
+ } catch (XMPPException e) {
+ throw new MessageHandlingException(message, e);
}
}
@@ -93,6 +93,7 @@ public class XmppMessageSendingMessageHandler extends AbstractMessageHandler imp
chat = xmppConnection.getChatManager().createChat(userId, thread, null);
}
}
+ Assert.notNull(chat, "Failed to obtain Chat instance");
return chat;
}
diff --git a/spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/config/XmppHeaderEnricherParserTests-context.xml b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/XmppHeaderEnricherParserTests-context.xml
similarity index 100%
rename from spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/config/XmppHeaderEnricherParserTests-context.xml
rename to spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/XmppHeaderEnricherParserTests-context.xml
diff --git a/spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/messages/ConsoleChatTests-context.xml b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/messages/ConsoleChatTests-context.xml
similarity index 93%
rename from spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/messages/ConsoleChatTests-context.xml
rename to spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/messages/ConsoleChatTests-context.xml
index cb029a34d0..0539c75c35 100644
--- a/spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/messages/ConsoleChatTests-context.xml
+++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/messages/ConsoleChatTests-context.xml
@@ -27,11 +27,7 @@
-
-
-
-
-
+
-
+
+ p:recipient="${recipient.address}"/>
- message = MessageBuilder.withPayload("Test Message").
+ setHeader(XmppHeaders.CHAT_TO_USER, "kermit@frog.com").
+ build();
+ // first Message
+ handler.handleMessage(message);
+
+ verify(chantManager, times(1)).createChat(Mockito.any(String.class), Mockito.any(MessageListener.class));
+ verify(chat, times(1)).sendMessage("Test Message");
+
+ // assuming we know thread ID although currently we do not provide this capability
+ message = MessageBuilder.withPayload("Hello Kitty").
+ setHeader(XmppHeaders.CHAT_TO_USER, "kermit@frog.com").
+ setHeader(XmppHeaders.CHAT_THREAD_ID, "123").
+ build();
+ reset(chat, chantManager);
+ when(chantManager.getThreadChat("123")).thenReturn(chat);
+
+ handler.handleMessage(message);
+ // in threaded conversation we need to look for existing chat
+ verify(chantManager, times(0)).createChat(Mockito.any(String.class), Mockito.any(MessageListener.class));
+ verify(chantManager, times(1)).getThreadChat("123");
+ verify(chat, times(1)).sendMessage("Hello Kitty");
+ }
+
+ @Test(expected=MessageHandlingException.class)
+ public void validateFailureNoChatToUser() throws Exception{
+ XmppMessageSendingMessageHandler handler = new XmppMessageSendingMessageHandler();
+ handler.handleMessage(new GenericMessage("hello"));
+ }
+
+ @Test(expected=MessageHandlingException.class)
+ public void validateMessageWithUnsupportedPayload() throws Exception{
+ XmppMessageSendingMessageHandler handler = new XmppMessageSendingMessageHandler();
+ handler.handleMessage(new GenericMessage(123));
+ }
+}
diff --git a/spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/presence/InboundXmppRosterEventsEndpointTests-context.xml b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/InboundXmppRosterEventsEndpointTests-context.xml
similarity index 100%
rename from spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/presence/InboundXmppRosterEventsEndpointTests-context.xml
rename to spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/InboundXmppRosterEventsEndpointTests-context.xml
diff --git a/spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/presence/OutboundXmppRosterEventsEndpointTests-context.xml b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/OutboundXmppRosterEventsEndpointTests-context.xml
similarity index 100%
rename from spring-integration-xmpp/src/test/resources/org/springframework/integration/xmpp/presence/OutboundXmppRosterEventsEndpointTests-context.xml
rename to spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/presence/OutboundXmppRosterEventsEndpointTests-context.xml
diff --git a/spring-integration-xmpp/src/test/resources/test.properties b/spring-integration-xmpp/src/test/java/test.properties
similarity index 100%
rename from spring-integration-xmpp/src/test/resources/test.properties
rename to spring-integration-xmpp/src/test/java/test.properties
diff --git a/src/docbkx/file.xml b/src/docbkx/file.xml
index fd1e0d48d1..005c164808 100644
--- a/src/docbkx/file.xml
+++ b/src/docbkx/file.xml
@@ -91,10 +91,10 @@
]]>
+ filename-pattern="test*" /> ]]>
The first channel adapter is relying on the default filter that just prevents
duplication, the second is using a custom filter, and the third is using the
- filename-pattern attribute to add a Pattern
+ filename-pattern attribute to add a AntPathMatcher
based filter to the FileReadingMessageSource.
The file-name-pattern and filter attributes are mutually exclusive, but
you can use a CompositeFileListFilter to use any combination of filters, including a
diff --git a/src/docbkx/gateway.xml b/src/docbkx/gateway.xml
index 382fb2416c..bae14f5092 100644
--- a/src/docbkx/gateway.xml
+++ b/src/docbkx/gateway.xml
@@ -236,11 +236,18 @@ For a more detailed example, please refer to the async-gatewayreply-timout is unbounded which means that
if not explicitly set there are several scenarios (described above) where your Gateway method invocation might
hang indefinitely, so make sure you analyze your flow and if there is even a remote possibility of one of these
- scenarios to occur in your flow, set the reply-timout to a 'safe' value at least for the sake
- of bringing method invocation to a close. But also, realize that there are some scenarios (see the very first one)
+ scenarios to occur, set the reply-timout attribute to a 'safe' value or better off
+ set the requires-reply attribute of the downstream component to 'true' to ensure a timely response.
+ But also, realize that there are some scenarios (see the very first one)
where reply-timout will not help which means it is also important to analyze your message
flow and decide when to use Sync Gateway vs Async Gateway where Gateway method invocation is always guaranteed
to return while giving you a more granular control over the results of the invocation via Java Futures.
+
+ Also, when dealing with Router you should remember that seeting resolution-required attribute to 'true'
+ will result in the exception thrown by the router if it can not resolve a particular chanel. And when dealing with the filter
+ you can also set throw-exception-on-rejection attribute. Both of these will help to ensure a timely response
+ from the Gateway method invocation.
+
diff --git a/src/docbkx/ip.xml b/src/docbkx/ip.xml
index eaab43ce35..552e6be8da 100644
--- a/src/docbkx/ip.xml
+++ b/src/docbkx/ip.xml
@@ -22,9 +22,6 @@
TCP inbound and outbound adapters are provided TcpSendingMessageHandler
sends messages over TCP. TcpReceivingChannelAdapter receives messages over TCP.
- If you have been using an earlier 2.0 milestone, note that the adapters are no longer configured
- with connection options directly; instead, they are given
- a reference to a connection factory. See below.
An inbound TCP gateway is provided; this allows for simple request/response processing. While
@@ -36,7 +33,8 @@
An outbound TCP gateway is provided; this allows for simple request/response processing.
If the associated connection factory is configured for single use connections, a new connection is
immediately created for each new request. Otherwise, if the connection is in use,
- the calling thread blocks on the connection until either a response is received or an I/O error occurs.
+ the calling thread blocks on the connection until either a response is received or a timeout
+ or I/O error occurs.
@@ -138,7 +136,7 @@
any incoming messages received on connections created by the outbound adapter.
- A server connection factory is used by an inbound channel adapter (in fact
+ A server connection factory is used by an inbound channel adapter or gateway (in fact
the connection factory will not function without one). A reference to a server
connection factory can also be provided to an outbound adapter; that adapter
can then be used to send replies to incoming messages to the same connection.
@@ -206,58 +204,124 @@
TCP is a streaming protocol; this means that some structure has to be provided to data
transported over TCP, so the receiver can demarcate the data into discrete messages.
- Connection factories are configured to use converters to convert between the message
- payload and the bits that are sent over TCP. This is accomplished by providing an
- input converter and output converter for inbound and outbound messages respectively.
- Four standard converters are provided; the first is ByteArrayCrlfConverter,
- which can convert a String or byte array to a stream of bytes followed by carriage
- return and linefeed characters (\r\n). This is the default converter and can be used with
- telnet as a client, for example. The second is is ByteArrayStxEtxConverter,
- which can convert a String or byte array to a stream of bytes preceded by an STX (0x02) and
- followed by an ETX (0x03). The third is ByteArrayLengthHeaderConverter,
- which can convert a String or byte array to a stream of bytes preceded by a 4 byte binary
- length in network byte order. Each of these converts an input stream containing the
- corresponding format to a byte array payload. The fourth converter is
- JavaSerializationConverter which can be used to convert any
- Serializable objects. We expect to provide other serialization technologies but you may also
- supply your own by implementing the InputStreamingConverter and
- OutputStreamingConverter interfaces. If you do not wish to use
- the default converters, you must supply input-converter and
- output-converter attributes on the connection factory (example below).
- This converter mechanism replaces the previous mechanism of subclassing the
- NxxSocketReader and NxxSocketWriter
+ Connection factories are configured to use (de)serializers to convert between the message
+ payload and the bits that are sent over TCP. This is accomplished by providing a
+ deserializer and serializer for inbound and outbound messages respectively.
+ Four standard (de)serializers are provided; the first is ByteArrayCrlfSerializer,
+ which can convert a byte array to a stream of bytes followed by carriage
+ return and linefeed characters (\r\n). This is the default (de)serializer and can be used with
+ telnet as a client, for example. The second is is ByteArrayStxEtxSerializer,
+ which can convert a byte array to a stream of bytes preceded by an STX (0x02) and
+ followed by an ETX (0x03). The third is ByteArrayLengthHeaderSerializer,
+ which can convert a byte array to a stream of bytes preceded by a 4 byte binary
+ length in network byte order. Each of these is a subclass of
+ AbstractByteArraySerializer which implements both
+ org.springframework.core.serializer.Serializer and
+ org.springframework.core.serializer.Deserializer.
+ For backwards compatibility, connections using any subclass of
+ AbstractByteArraySerializer for serialization
+ will also accept a String which will be converted to a byte array first.
+ Each of these (de)serializers converts an input stream containing the
+ corresponding format to a byte array payload. The fourth standard serializer is
+ org.springframework.core.serializer.DefaultSerializer which can be
+ used to convert Serializable objects using java serialization.
+ org.springframework.core.serializer.DefaultDeserializer is provided for
+ inbound deserialization of streams containing Serializable objects.
+ To implement a custom (de)serializer pair, implement the
+ org.springframework.core.serializer.Deserializer and
+ org.springframework.core.serializer.Serializer interfaces. If you do not wish to use
+ the default (de)serializer (ByteArrayCrLfSerializer), you must supply
+ serializer and
+ deserializer attributes on the connection factory (example below).
+
+ ]]>
A server connection factory that uses java.net.Socket
connections and uses Java serialization on the wire.
-
- Normally, with shared connections, one would expect the the same wire protocol
- to be used for both inbound and outbound messages; however, the configuration
- allows them to be different. Note, however that if you only specify one converter
- the other direction will use the default converter.
-
+ For full details of the attributes available on connection factories, see the
+ reference at the end of this section.
+
+
+ Tcp Connection Interceptors
Connection factories can be configured with a reference to a
TcpConnectionInterceptorFactoryChain. Interceptors can be used
to add behavior to connections, such as negotiation, security, and other setup.
- Further documentation to follow.
+ No interceptors are currently provided by the framework but, for an example,
+ see the InterceptedSharedConnectionTests in the source
+ repository.
- For a full reference of the attributes available on connection factories, see the
- reference at the end of this section.
+ The HelloWorldInterceptor used in the test case works as follows:
+
+
+ When configured with a client connection factory,
+ when the first message is sent over a connection that is intercepted, the interceptor
+ sends 'Hello' over the connection, and expects to receive 'world!'. When that occurs,
+ the negotiation is complete and the original message is sent; further messages
+ that use the same connection are sent without any additional negotiation.
+
+
+ When configured with a server connection factory, the interceptor requires the first
+ message to be 'Hello' and, if it is, returns 'world!'. Otherwise it throws an exception causing
+ the connection to be closed.
+
+
+ All TcpConnection methods are intercepted.
+ Interceptor instances are created for each connection by an interceptor factory.
+ If an interceptor is stateful, the factory should create a new instance for each connection.
+ Interceptor
+ factories are added to the configuration of an interceptor factory chain, which is provided
+ to a connection factory using the interceptor-factory attribute.
+ Interceptors must implement the TcpConnectionInterceptor interface;
+ factories
+ must implement the TcpConnectionInterceptorFactory interface. A
+ convenience class AbstractTcpConnectionInterceptor is provided
+ with passthrough methods; by extending this class, you only need to implement those
+ methods you wish to intercept.
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+ Configuring a connection interceptor factory chain.
@@ -268,7 +332,7 @@
connection-factory and channel.
The channel attribute specifies the channel on which messages arrive at an
outbound adapter and on which messages are placed by an inbound adapter.
- The connection factory indicates which connection factory is to be used to
+ The connection-factory attribute indicates which connection factory is to be used to
manage connections for the adapter. While both inbound and outbound adapters
can share a connection factory, server connection factories are always 'owned'
by an inbound adapter; client connection factories are always 'owned' by an
@@ -277,13 +341,16 @@
+
+
@@ -294,8 +361,8 @@
port="#{server.port}"
single-use="true"
so-timeout="10000"
- input-converter="serializer"
- output-converter="serializer"
+ deserializer="javaDeserializer"
+ serializer="javaSerializer"
/>
@@ -326,7 +393,8 @@
at the server and placed on channel 'loop'. Since 'loop' is
the input channel for 'outboundServer' the message is simply
looped back over the same connection and received by
- 'inboundClient' and deposited in channel 'replies'.
+ 'inboundClient' and deposited in channel 'replies'. Java
+ serialization is used on the wire.
@@ -338,15 +406,15 @@
can process a single request/response at a time.
- After constructing a message with the incoming payload and sending
- it to the requestChannel, it waits for a response and sends the payload
+ The intbound gateway, after constructing a message with the incoming payload and sending
+ it to the requestChannel, waits for a response and sends the payload
from the response message by writing it to the connection.
- After sending a message over the connection, the thread waits for a response and
- constructs a response message
+ The outbound gateway, after sending a message over the connection, waits for a response and
+ constructs a response message and puts in on the reply channel.
Communications over the connections are single-threaded. Users should be aware that only one
- message can be handled at a time and if another thread attempts to send
+ message can be handled at a time and, if another thread attempts to send
a message before the current response has been received, it will block until
any previous requests are complete (or time out).
If, however, the client connection factory is configured for single-use connections
@@ -360,8 +428,8 @@
connection-factory="cfServer"
reply-timeout="10000"
/>]]>
- A simple inbound TCP gateway; if a default connection factory is used,
- messages will be \r\n delimited data and the gateway can be
+ A simple inbound TCP gateway; if a connection factory configured with the default
+ (de)serializer is used, messages will be \r\n delimited data and the gateway can be
used by a simple client such as telnet.
@@ -419,6 +487,22 @@
The port.
+
+ serializer
+ Y
+ Y
+
+ An implementation of Serializer used to serialize
+ the payload. Defaults to ByteArrayCrLfSerializer
+
+
+ deserializer
+ Y
+ Y
+
+ An implementation of Deserializer used to deserialize
+ the payload. Defaults to ByteArrayCrLfSerializer
+ using-nioY
@@ -542,7 +626,7 @@
UDP Outbound Channel Adapter Attributes
-
+
@@ -639,8 +723,6 @@
local-address
- N
- YOn a multi-homed system, for the UDP adapter, specifies an IP address
for the interface to which the socket will be bound for reply messages.
@@ -661,7 +743,7 @@
UDP Inbound Channel Adapter Attributes
-
+
@@ -735,8 +817,6 @@
so-receive-buffer- size
- Y
- YSee java.net.DatagramSocket
setReceiveBufferSize() for more information.
diff --git a/src/docbkx/message-publishing.xml b/src/docbkx/message-publishing.xml
index b196a066cb..89993ac699 100644
--- a/src/docbkx/message-publishing.xml
+++ b/src/docbkx/message-publishing.xml
@@ -299,64 +299,70 @@ static class BankingOperationsImpl implements BankingOperations {
- Producing and publishing messages based on schedule
+ Producing and publishing messages based on a scheduled trigger
In the above sections we looked at the Message publishing feature of Spring Integration which constructs and publishes messages as by-products of Method invocations.
- However you are still responsible to invoke the method.
- With scheduling support added to Spring Framework 3.0 we've added another useful feature to Spring Integration - support for scheduled Message producers/publishers. Scheduling could be based on several triggers.
- Currently we support cron, fixed-rate, fixed-delay as well as the custom triggers implemented by you.
+ However in that case, you are still responsible for invoking the method.
+ In Spring Integration 2.0 we've added another related useful feature: support for scheduled Message producers/publishers via the new "expression" attribute
+ on the 'inbound-channel-adapter' element. Scheduling could be based on several triggers, any one of which may be configured on the 'poller' sub-element.
+ Currently we support cron, fixed-rate, fixed-delay as well as any custom trigger implemented by you.
- Support for scheduled producers/publishers is provided via <scheduled-producer> xml element.
- Lets look at couple of examples:
+ As mentioned above, support for scheduled producers/publishers is provided via the <inbound-channel-adapter> xml element.
+ Let's look at couple of examples:
- ]]>
+
+
+]]>
- In the above example scheduled producer will be created which will construct the Message with payload being the result of the expression
- defined in payload-expression attribute. Such message will be created and sent every time after a delay specified in the fixed-delay attribute.
+ In the above example an inbound Channel Adapter will be created which will construct a Message with its payload being the result of the expression
+ defined in the expression attribute. Such message will be created and sent every time after the delay specified by the fixed-delay attribute.
+
+
+]]>
- ]]>
-
- This example is very similar to the previous one, except that we are using fixed-rate attribute which will allow us to send messages at the fixed rate.
+ This example is very similar to the previous one, except that we are using the fixed-rate attribute which will allow us to send messages at a fixed rate (measuring from the start time of each task).
- ]]>
+
+
+]]>
- This example demonstrates how you can apply Cron trigger specified by cron attribute.
+ This example demonstrates how you can apply a Cron trigger with a value specified in the cron attribute.
-
-
-
-]]>
+
+
+
+]]>
- Here you can see that in a way very similar to Message publishing feature we are enriching a newly constructed Message with
- extra Message headers which could take scalar values as well as Spring expressions.
+ Here you can see that in a way very similar to the Message publishing feature we are enriching a newly constructed Message with
+ extra Message headers which could take scalar values as well as the results of evaluating Spring expressions.
- If you need to implement your own custom trigger you can use trigger attribute pointing to any spring configured
- bean which implements org.springframework.scheduling.Trigger interface.
+ If you need to implement your own custom trigger you can use the trigger attribute to provide a reference to any spring configured
+ bean which implements the org.springframework.scheduling.Trigger interface.
-
+
+
+
-
+ ]]>
diff --git a/src/docbkx/router.xml b/src/docbkx/router.xml
index 5bb0fa182e..c089a3960b 100644
--- a/src/docbkx/router.xml
+++ b/src/docbkx/router.xml
@@ -201,5 +201,206 @@ public List<String> route(@Header("orderStatus") OrderStatus status)
For routing of XML-based Messages, including XPath support, see .
+
+
+ Dynamic Routers
+
+ So as you can see, Spring Integration provides quite a few different router configurations for most common
+ content-based routing use cases as well as the option of implementing custom routers as POJOs.
+ For example; Payload Type Router provides a simple way to configure a router which computes channels
+ based on the payload type of the incoming Message while Header Value Router provides the
+ same convenience in configuring a router which computes channels based on evaluating the value
+ of a particular Message Header. There is also an expression-based (SpEL) routers where the channel
+ is determined based on evaluating an expression which gives these type of routers some dynamic characteristics.
+
+
+ However these routers share one common attribute - static configuration. Even in the case of
+ expression-based routers, the expression itself is defined as part of the router configuration which means that
+ the same expression operating on the same value will always result in the computation of the same channel.
+ This is good in most cases since such routes are well defined and therefore predictable. But there are times when we
+ need to change router configurations dynamically so message flows could be routed to a different channel.
+
+ For example:
+
+ You might want to bring down some part of your system for maintenance. So, temporarily you want to re-reroute
+ messages to a different message flow. Or you may want to introduce more granularity to your message flow by adding another
+ route to handle a more concrete type of java.lang.Number (in cases of Payload Type Router).
+
+
+ Unfortunately with static router configuration to accomplish this you'd have to bring down your entire application,
+ change the configuration of the router (change routes) and bring it back up. This is obviously not the solution.
+
+
+
+ Dynamic Router
+
+ pattern describes the mechanisms by which one can change/configure routers dynamically without
+ bringing down your system or individual routers.
+
+
+ Before we get into the specifics of how it is accomplished in Spring Integration lets quickly summarize the
+ typical flow of the router, which consists of 3 simple steps:
+
+
+ Step 1 - Compute channel identifier which is a value calculated by the
+ router once it receives the Message. Typically it is a String or and instance of the actual
+ MessageChannel.
+
+
+ Step 2 - Resolve channel identifier to channel name. We'll describe
+ specifics of this process in a moment.
+
+
+ Step 3 - Resolve channel name to the actual MessageChannel
+
+
+
+
+
+ There is not much that could be done with regard to router dynamics if Step 1 results in the actual instance of the
+ MessageChannel simply because MessageChannel is the final product of any
+ router's job. However, if Step 1 results in channel identifier that is not and instance of MessageChannel,
+ then there are quite a few possibilities to influence the process of calculating what will be the final instance of the Message Channel.
+ Lets look at couple of the examples in the context of the 3 steps mentioned above:
+
+
+ Payload Type Router
+
+
+
+
+
+]]>
+
+
+ Within the context of the Payload Type Router the 3 steps mentioned above would be realized as:
+
+
+ Step 1 - Compute channel identifier which is the fully qualified name of the payload type
+ (e.g., java.lang.String).
+
+
+ Step 2 - Resolve channel identifier to channel name where
+ the result of the previous step is used to select the appropriate value from the payload type mapping
+ defined via mapping element.
+
+
+ Step 3 - Resolve channel name to the actual instance of the
+ MessageChannel where using ChannelResolver router will obtain a
+ reference to a bean (which is hopefully a MessageChannel) identified by the result of the
+ previous step.
+
+
+ In other words each step feeds the next step until thr process completes.
+
+
+ Header Value Router
+
+
+
+
+
+]]>
+
+
+ Similar to the PayloadTypeRouter:
+
+
+ Step 1 - Compute channel identifier which is the value of the header identified by the
+ header-name attribute.
+
+
+ Step 2 - Resolve channel identifier to channel name where
+ the result of the previous step is used to select the appropriate value from the general mapping
+ defined via mapping element.
+
+
+ Step 3 - Resolve channel name to the actual instance of the
+ MessageChannel where using ChannelResolver router will obtain a
+ reference to a bean (which is hopefully a MessageChannel) identified by the result of the
+ previous step.
+
+
+
+
+ The above two configurations of two different router types look almost identical.
+ However if we look at the different configuration of the HeaderValueRouter we clearly see that
+ there is no mapping sub element:
+ ]]>
+ But configuration is still perfectly valid. So the natural question is what about the maping in the Step 2?
+
+
+ What this means is that Step 2 is now an optional step. If mapping is not defined then the channel identifier
+ value computed in Step 1 will automatically be treated as the channel name which will now be resolved to the
+ actual MessageChannel in the Step 3. What it also means is that Step 2 is one of the key steps to
+ provide dynamic characteristics to the routers, since it introduces a process which
+ allows you to change the way 'channel identifier' resolves to 'channel name',
+ thus influencing the process of determining the final instance of the MessageChannel from the initial
+ channel identifier.
+
+ For Example:
+
+ In the above configuration lets assume that the testHeader value is 'kermit' which is now a channel identifier
+ (Step 1). Since there is no mapping in this router, resolving this channel identifier to a channel name
+ (Step 2) is impossible and this channel identifier is now treated as channel name. However what if
+ there was mapping but for a different value, the end result would still be the same and that is:
+ if new value can not be determined through the process of resolving 'channel identifier' to a 'channel name',
+ such 'channel identifier' becomes 'channel name'
+
+
+ So all that is left is for Step 3 to resolve channel name ('kermit') to an actual instance of the
+ MessageChannel identified by this name. That will be done via default
+ ChannelResolver implementation which is BeanFactoryChannelResolver which
+ basically does a bean lookup by the name provided. So now all messages which contain the header/value pair as testHeader=kermit
+ are going to be routed to a 'kermit' MessageChannel.
+
+
+ But what if you want to route these messages to 'simpson' channel? Obviously changing static configuration would work,
+ but would also require bringing your system down. However if you had access to channel identifier map, then you
+ could just introduce a new mapping where header/value pair is now kermit=simpson, thus allowing Step 2 to treat
+ 'kermit' as channel identifier while resolving it to 'simpson' as channel name .
+
+
+ The same obviously applies for PayloadTypeRouter where you can now remap or remove a particular payload type
+ mapping, and every other router including expression-based routers since their computed value
+ will now have a chance to go through Step 2 to be aditionally resolved to the actual channel name.
+
+
+ In Spring Integration 2.0 routers hierarchy underwent major refactoring and now any router that is a subclass of the
+ AbstractMessageRouter (all framework defined routers) is a Dynamic Router simply because
+ channelIdentiferMap is defined at the AbstractMessageRouter with convenient accessors
+ and modifiers exposed as public methods allowing you to change/add/remove router mapping at runtime via JMX (see section section 29) or
+ ControlBus (see section section 29.7) functionality.
+
+
+
+ Control Bus
+
+
+ One of the way to manage the router mappings is through the Control Bus
+ which exposes a Control Channel where you can send
+ control messages to manage and monitor Spring Integration components which includes routers.
+ For more information about the Control Bus see section 29.7. Typically you would send a control message asking to invoke a
+ particular JMX operation on a particular managed component (e.g., router). The two managed operations (methods) that are
+ specific to changing router resolution process are:
+
+
+ public void setChannelMapping(String channelIdentifier, String channelName) -
+ will allow you to add new or modify existing mapping of channel identifier to channel name
+
+
+ public void removeChannelMapping(String channelIdentifier) -
+ will allow you to remove a particular channel mapping, thus disconnecting the relationship between
+ channel identifier and channel name
+
+
+ There are obviously other managed operations, so please refer to an AbstractMessageRouter for more detail
+
+
+ You can also use your favorite JMX client (e.g., JConsole) and use those operations (methods) to change
+ router configuration. For more information on Spring Integration management and monitoring please visit
+ section 29 of this manual.
+
+
\ No newline at end of file