diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java
index 37dd1d7542..e53e0e5b42 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandler.java
@@ -21,6 +21,7 @@ import java.util.concurrent.locks.Lock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.EiMessageHeaderAccessor;
import org.springframework.integration.channel.NullChannel;
@@ -127,6 +128,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
public void setMessageStore(MessageGroupStore store) {
this.messageStore = store;
store.registerMessageGroupExpiryCallback(new MessageGroupCallback() {
+ @Override
public void execute(MessageGroupStore messageGroupStore, MessageGroup group) {
forceComplete(group);
}
@@ -144,6 +146,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
sequenceAware = this.releaseStrategy instanceof SequenceSizeReleaseStrategy;
}
+ @Override
public void setOutputChannel(MessageChannel outputChannel) {
Assert.notNull(outputChannel, "'outputChannel' must not be null");
this.outputChannel = outputChannel;
@@ -268,17 +271,32 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
try {
lock.lockInterruptibly();
try {
+ MessageGroup groupNow = group;
/*
- * Refetch the group because it might have changed while we were waiting on
+ * If the group argument is not already complete,
+ * re-fetch it because it might have changed while we were waiting on
* its lock. If the last modified timestamp changed, defer the completion
* because the selection condition may have changed such that the group
- * would no longer be eligible.
+ * would no longer be eligible. If the timestamp changed, it's a completely new
+ * group and should not be reaped on this cycle.
+ *
+ * If the group argument is already complete, do not re-fetch.
+ * Note: not all message stores provide a direct reference to its internal
+ * group so the initial 'isComplete()` will only return true for those stores if
+ * the group was already complete at the time of its selection as a candidate.
+ *
+ * If the group is marked complete, only consider it
+ * for reaping if it's empty (and both timestamps are unaltered).
*/
- MessageGroup groupNow = this.messageStore.getMessageGroup(
- group.getGroupId());
+ if (!group.isComplete()) {
+ groupNow = this.messageStore.getMessageGroup(correlationKey);
+ }
long lastModifiedNow = groupNow.getLastModified();
- if (group.getLastModified() == lastModifiedNow) {
- if (groupNow.size() > 0) {
+ int groupSize = groupNow.size();
+ if ((!groupNow.isComplete() || groupSize == 0)
+ && group.getLastModified() == lastModifiedNow
+ && group.getTimestamp() == groupNow.getTimestamp()) {
+ if (groupSize > 0) {
if (releaseStrategy.canRelease(groupNow)) {
this.completeGroup(correlationKey, groupNow);
}
@@ -306,7 +324,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
}
}
}
- finally {
+ finally {
if (removeGroup) {
this.remove(group);
}
@@ -347,7 +365,8 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageH
+ correlationKey + "] to: " + outputChannel);
}
completeGroup(correlationKey, group);
- } else {
+ }
+ else {
if (logger.isDebugEnabled()) {
logger.debug("Discarding messages of partially complete group with key ["
+ correlationKey + "] to: " + discardChannel);
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultHeaderChannelRegistry.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultHeaderChannelRegistry.java
new file mode 100644
index 0000000000..065d4fc31e
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/DefaultHeaderChannelRegistry.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.channel;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.springframework.context.SmartLifecycle;
+import org.springframework.integration.context.IntegrationObjectSupport;
+import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
+import org.springframework.integration.support.channel.HeaderChannelRegistry;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.util.Assert;
+
+/**
+ * Converts a channel to a name, retaining a reference to the channel keyed by the name.
+ * Allows a downstream {@link BeanFactoryChannelResolver} to find the channel by name
+ * in the event that the flow serialized the message at some point.
+ * Channels are expired after a configurable delay (60 seconds by default).
+ * The actual average expiry time will be 1.5x the delay.
+ *
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public class DefaultHeaderChannelRegistry extends IntegrationObjectSupport
+ implements HeaderChannelRegistry, SmartLifecycle, Runnable {
+
+ private static final int DEFAULT_REAPER_DELAY = 60000;
+
+ private final Map channels = new ConcurrentHashMap();
+
+ private static final AtomicLong id = new AtomicLong();
+
+ private final String uuid = UUID.randomUUID().toString() + ":";
+
+ private volatile long reaperDelay;
+
+ private volatile ScheduledFuture> reaperScheduledFuture;
+
+ private volatile boolean running;
+
+ private volatile int phase;
+
+ private volatile boolean autoStartup = true;
+
+ /**
+ * Constructs a registry with the default delay for channel expiry.
+ */
+ public DefaultHeaderChannelRegistry() {
+ this(DEFAULT_REAPER_DELAY);
+ }
+
+ /**
+ * Constructs a registry with the provided delay (milliseconds) for
+ * channel expiry.
+ *
+ * @param reaperDelay the delay in milliseconds.
+ */
+ public DefaultHeaderChannelRegistry(long reaperDelay) {
+ this.setReaperDelay(reaperDelay);
+ }
+
+ /**
+ * Set the reaper delay.
+ *
+ * @param reaperDelay the delay in milliseconds.
+ */
+ public final void setReaperDelay(long reaperDelay) {
+ Assert.isTrue(reaperDelay > 0, "'reaperDelay' must be > 0");
+ this.reaperDelay = reaperDelay;
+ }
+
+ public final long getReaperDelay() {
+ return reaperDelay;
+ }
+
+ @Override
+ public void setTaskScheduler(TaskScheduler taskScheduler) {
+ super.setTaskScheduler(taskScheduler);
+ }
+
+ @Override
+ public int getPhase() {
+ return this.phase;
+ }
+
+ public final void setPhase(int phase) {
+ this.phase = phase;
+ }
+
+ @Override
+ public boolean isAutoStartup() {
+ return this.autoStartup;
+ }
+
+ public final void setAutoStartup(boolean autoStartup) {
+ this.autoStartup = autoStartup;
+ }
+
+ @Override
+ public final int size() {
+ return this.channels.size();
+ }
+
+ @Override
+ protected void onInit() throws Exception {
+ super.onInit();
+ Assert.notNull(this.getTaskScheduler(), "a task scheduler is required");
+ }
+
+ @Override
+ public synchronized void start() {
+ if (!this.running) {
+ Assert.notNull(this.getTaskScheduler(), "a task scheduler is required");
+ this.reaperScheduledFuture = this.getTaskScheduler().schedule(this,
+ new Date(System.currentTimeMillis() + this.reaperDelay));
+ this.running = true;
+ }
+ }
+
+ @Override
+ public synchronized void stop() {
+ this.running = false;
+ if (this.reaperScheduledFuture != null) {
+ this.reaperScheduledFuture.cancel(true);
+ }
+ }
+
+ @Override
+ public void stop(Runnable callback) {
+ this.stop();
+ callback.run();
+ }
+
+ @Override
+ public boolean isRunning() {
+ return this.running;
+ }
+
+ @Override
+ public Object channelToChannelName(Object channel) {
+ if (channel != null && channel instanceof MessageChannel) {
+ String name = this.uuid + DefaultHeaderChannelRegistry.id.incrementAndGet();
+ channels.put(name, new MessageChannelWrapper((MessageChannel) channel));
+ if (logger.isDebugEnabled()) {
+ logger.debug("Registered " + channel + " as " + name);
+ }
+ return name;
+ }
+ else {
+ return channel;
+ }
+ }
+
+ @Override
+ public MessageChannel channelNameToChannel(String name) {
+ if (name != null) {
+ MessageChannelWrapper messageChannelWrapper = this.channels.get(name);
+ if (logger.isDebugEnabled() && messageChannelWrapper != null) {
+ logger.debug("Retrieved " + messageChannelWrapper.getChannel() + " with " + name);
+ }
+ return messageChannelWrapper == null ? null : messageChannelWrapper.getChannel();
+ }
+ return null;
+ }
+
+ /**
+ * Cancel the scheduled reap task and run immediately; then reschedule.
+ */
+ @Override
+ public void runReaper() {
+ synchronized(this) {
+ this.reaperScheduledFuture.cancel(false);
+ this.reaperScheduledFuture = null;
+ }
+ this.run();
+ }
+
+ @Override
+ public void run() {
+ this.reaperScheduledFuture = null;
+ if (logger.isTraceEnabled()) {
+ logger.trace("Reaper started; channels size=" + this.channels.size());
+ }
+ Iterator> iterator = this.channels.entrySet().iterator();
+ long threshold = System.currentTimeMillis() - this.reaperDelay;
+ while (iterator.hasNext()) {
+ Entry entry = iterator.next();
+ if (entry.getValue().getCreated() < threshold) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Expiring " + entry.getKey() + " (" + entry.getValue().getChannel() + ")");
+ }
+ iterator.remove();
+ }
+ }
+ synchronized (this) {
+ if (this.reaperScheduledFuture == null) {
+ this.reaperScheduledFuture = this.getTaskScheduler().schedule(this,
+ new Date(System.currentTimeMillis() + this.reaperDelay));
+ }
+ }
+ if (logger.isTraceEnabled()) {
+ logger.trace("Reaper completed; channels size=" + this.channels.size());
+ }
+ }
+
+
+ private class MessageChannelWrapper {
+
+ private final MessageChannel channel;
+
+ private final long created;
+
+ private MessageChannelWrapper(MessageChannel channel) {
+ this.channel = channel;
+ this.created = System.currentTimeMillis();
+ }
+
+ public final long getCreated() {
+ return created;
+ }
+
+ public final MessageChannel getChannel() {
+ return channel;
+ }
+
+ }
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java
index 104d17e7e4..dc4d8a1164 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractIntegrationNamespaceHandler.java
@@ -16,7 +16,10 @@
package org.springframework.integration.config.xml;
-import static org.springframework.integration.context.IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -26,8 +29,10 @@ import org.w3c.dom.Node;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedSet;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
@@ -35,9 +40,14 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
import org.springframework.integration.config.xml.ChannelInitializer.AutoCreateCandidatesCollector;
import org.springframework.integration.context.IntegrationContextUtils;
+import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.expression.IntegrationEvaluationContextAwareBeanPostProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -68,15 +78,53 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
private final NamespaceHandlerDelegate delegate = new NamespaceHandlerDelegate();
+ @Override
public final BeanDefinition parse(Element element, ParserContext parserContext) {
this.verifySchemaVersion(element, parserContext);
this.registerImplicitChannelCreator(parserContext);
this.registerIntegrationEvaluationContext(parserContext);
+ this.registerIntegrationProperties(parserContext);
+ this.registerHeaderChannelRegistry(parserContext);
this.registerBuiltInBeans(parserContext);
this.registerDefaultConfiguringBeanFactoryPostProcessorIfNecessary(parserContext);
return this.delegate.parse(element, parserContext);
}
+ private void registerIntegrationProperties(ParserContext parserContext) {
+
+ boolean alreadyRegistered = false;
+ BeanDefinitionRegistry registry = parserContext.getRegistry();
+ if (registry instanceof ListableBeanFactory) {
+ alreadyRegistered = ((ListableBeanFactory) registry)
+ .containsBean(IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME);
+ }
+ else {
+ alreadyRegistered = registry.isBeanNameInUse(IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME);
+ }
+ if (!alreadyRegistered) {
+ ResourcePatternResolver resourceResolver =
+ new PathMatchingResourcePatternResolver(parserContext.getReaderContext().getBeanClassLoader());
+ try {
+ Resource[] defaultResources = resourceResolver.getResources("classpath*:META-INF/spring.integration.default.properties");
+ Resource[] userResources = resourceResolver.getResources("classpath*:META-INF/spring.integration.properties");
+
+ List resources = new LinkedList(Arrays.asList(defaultResources));
+ resources.addAll(Arrays.asList(userResources));
+
+ BeanDefinitionBuilder integrationPropertiesBuilder = BeanDefinitionBuilder
+ .genericBeanDefinition(PropertiesFactoryBean.class)
+ .addPropertyValue("locations", resources);
+
+ registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME,
+ integrationPropertiesBuilder.getBeanDefinition());
+ }
+ catch (IOException e) {
+ parserContext.getReaderContext().warning("Cannot load 'spring.integration.properties' Resources.", null, e);
+ }
+ }
+ }
+
+ @Override
public final BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext) {
return this.delegate.decorate(source, definition, parserContext);
}
@@ -98,7 +146,10 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(CHANNEL_INITIALIZER_BEAN_NAME);
}
if (!alreadyRegistered) {
- BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition(ChannelInitializer.class);
+ String channelsAutoCreateExpression = "#{@" +IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME +
+ "['" + IntegrationProperties.CHANNELS_AUTOCREATE + "']}";
+ BeanDefinitionBuilder channelDef = BeanDefinitionBuilder.genericBeanDefinition(ChannelInitializer.class)
+ .addPropertyValue("autoCreate", channelsAutoCreateExpression);
BeanDefinitionHolder channelCreatorHolder = new BeanDefinitionHolder(channelDef.getBeanDefinition(), CHANNEL_INITIALIZER_BEAN_NAME);
BeanDefinitionReaderUtils.registerBeanDefinition(channelCreatorHolder, parserContext.getRegistry());
}
@@ -127,10 +178,11 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
// unlike DefaultConfiguringBeanFactoryPostProcessor, we need one of these per registry
// therefore we need to call containsBeanDefinition(..) which does not consider the parent registry
alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBeanDefinition(
- INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
+ IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
}
else {
- alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
+ alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(
+ IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME);
}
if (!alreadyRegistered) {
BeanDefinitionBuilder integrationEvaluationContextBuilder = BeanDefinitionBuilder
@@ -171,6 +223,31 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
}
}
+ String xpathBeanName = "xpath";
+ alreadyRegistered = false;
+ if (parserContext.getRegistry() instanceof ListableBeanFactory) {
+ alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry()).containsBean(xpathBeanName);
+ }
+ else {
+ alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(xpathBeanName);
+ }
+ if (!alreadyRegistered) {
+ Class> xpathClass = null;
+ try {
+ xpathClass = ClassUtils.forName(IntegrationNamespaceUtils.BASE_PACKAGE + ".xml.xpath.XPathUtils",
+ parserContext.getReaderContext().getBeanClassLoader());
+ }
+ catch (ClassNotFoundException e) {
+ logger.debug("SpEL function '#xpath' isn't registered: there is no spring-integration-xml.jar on the classpath.");
+ }
+
+ if (xpathClass != null) {
+ IntegrationNamespaceUtils.registerSpelFunctionBean(parserContext.getRegistry(), xpathBeanName,
+ IntegrationNamespaceUtils.BASE_PACKAGE + ".xml.xpath.XPathUtils", "evaluate");
+ }
+ }
+
+
this.doRegisterBuiltInBeans(parserContext);
}
@@ -195,6 +272,33 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
}
}
+ /**
+ * Register a DefaultHeaderChannelRegistry in the given BeanDefinitionRegistry, if necessary.
+ */
+ private void registerHeaderChannelRegistry(ParserContext parserContext) {
+ boolean alreadyRegistered = false;
+ if (parserContext.getRegistry() instanceof ListableBeanFactory) {
+ alreadyRegistered = ((ListableBeanFactory) parserContext.getRegistry())
+ .containsBean(IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME);
+ }
+ else {
+ alreadyRegistered = parserContext.getRegistry().isBeanNameInUse(
+ IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME);
+ }
+ if (!alreadyRegistered) {
+ if (logger.isInfoEnabled()) {
+ logger.info("No bean named '" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME +
+ "' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.");
+ }
+ BeanDefinitionBuilder schedulerBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultHeaderChannelRegistry.class);
+ BeanDefinitionHolder replyChannelRegistryComponent = new BeanDefinitionHolder(
+ schedulerBuilder.getBeanDefinition(),
+ IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME);
+ BeanDefinitionReaderUtils.registerBeanDefinition(replyChannelRegistryComponent, parserContext.getRegistry());
+ }
+ }
+
+
protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator decorator) {
this.delegate.doRegisterBeanDefinitionDecorator(elementName, decorator);
}
@@ -225,6 +329,7 @@ public abstract class AbstractIntegrationNamespaceHandler implements NamespaceHa
private class NamespaceHandlerDelegate extends NamespaceHandlerSupport {
+ @Override
public void init() {
AbstractIntegrationNamespaceHandler.this.init();
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java
index 6da6aec529..2e7d981a87 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractPollingInboundChannelAdapterParser.java
@@ -37,7 +37,6 @@ import org.springframework.util.xml.DomUtils;
public abstract class AbstractPollingInboundChannelAdapterParser extends AbstractChannelAdapterParser {
@Override
- @SuppressWarnings("unchecked")
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
BeanMetadataElement source = this.parseSource(element, parserContext);
if (source == null) {
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java
index 5dba32f685..fba8d51b2e 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java
@@ -29,11 +29,12 @@ import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.integration.context.IntegrationContextUtils;
+import org.springframework.integration.expression.DynamicExpression;
+import org.springframework.integration.transformer.HeaderEnricher;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
-import org.springframework.integration.expression.DynamicExpression;
-import org.springframework.integration.transformer.HeaderEnricher;
/**
* Base support class for 'header-enricher' parsers.
@@ -41,6 +42,7 @@ import org.springframework.integration.transformer.HeaderEnricher;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
+ * @author Gary Russell
* @since 2.0
*/
public abstract class HeaderEnricherParserSupport extends AbstractTransformerParser {
@@ -49,6 +51,16 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
private final Map> elementToTypeMap = new HashMap>();
+ private final static Map cannedHeaderElementExpressions = new HashMap();
+
+ static {
+ cannedHeaderElementExpressions.put("header-channels-to-string", new String[][] {
+ {"replyChannel", "@" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME
+ + ".channelToChannelName(headers.replyChannel)" },
+ {"errorChannel", "@" + IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME
+ + ".channelToChannelName(headers.errorChannel)" },
+ });
+ }
@Override
protected final String getTransformerClassName() {
@@ -86,6 +98,8 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
Element headerElement = (Element) node;
String elementName = node.getLocalName();
Class> headerType = null;
+ String expression = null;
+ String overwrite = headerElement.getAttribute("overwrite");
if ("header".equals(elementName)) {
headerName = headerElement.getAttribute(NAME_ATTRIBUTE);
}
@@ -114,135 +128,157 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar
}
}
}
- if (headerName != null) {
- String value = headerElement.getAttribute("value");
- String ref = headerElement.getAttribute(REF_ATTRIBUTE);
- String method = headerElement.getAttribute(METHOD_ATTRIBUTE);
- String expression = headerElement.getAttribute(EXPRESSION_ATTRIBUTE);
-
- Element beanElement = null;
- Element scriptElement = null;
- Element expressionElement = null;
-
- List subElements = DomUtils.getChildElements(headerElement);
- if (!subElements.isEmpty()) {
- Element subElement = subElements.get(0);
- String subElementLocalName = subElement.getLocalName();
- if ("bean".equals(subElementLocalName)) {
- beanElement = subElement;
- }
- else if ("script".equals(subElementLocalName)) {
- scriptElement = subElement;
- }
- else if ("expression".equals(subElementLocalName)) {
- expressionElement = subElement;
- }
- if (beanElement == null && scriptElement == null && expressionElement == null) {
- parserContext.getReaderContext().error("Only 'bean', 'script' or 'expression' can be defined as a sub-element", element);
+ if (headerName == null) {
+ if (cannedHeaderElementExpressions.containsKey(elementName)) {
+ for (int j = 0; j < cannedHeaderElementExpressions.get(elementName).length; j++) {
+ headerName = cannedHeaderElementExpressions.get(elementName)[j][0];
+ expression = cannedHeaderElementExpressions.get(elementName)[j][1];
+ overwrite = "true";
+ this.addHeader(element, headers, parserContext, headerName, headerElement, headerType,
+ expression, overwrite);
}
}
- if (StringUtils.hasText(expression) && expressionElement != null) {
- parserContext.getReaderContext().error("The 'expression' attribute and sub-element are mutually exclusive", element);
- }
-
- boolean isValue = StringUtils.hasText(value);
- boolean isRef = StringUtils.hasText(ref);
- boolean hasMethod = StringUtils.hasText(method);
- boolean isExpression = StringUtils.hasText(expression) || expressionElement != null;
- boolean isScript = scriptElement != null;
-
- BeanDefinition innerComponentDefinition = null;
-
- if (beanElement != null) {
- innerComponentDefinition = parserContext.getDelegate().parseBeanDefinitionElement(beanElement).getBeanDefinition();
- }
- else if (isScript) {
- innerComponentDefinition = parserContext.getDelegate().parseCustomElement(scriptElement);
- }
-
- boolean isCustomBean = innerComponentDefinition != null;
-
- if (hasMethod && isScript) {
- parserContext.getReaderContext().error("The 'method' attribute cannot be used when a 'script' sub-element is defined", element);
- }
-
- if (!(isValue ^ (isRef ^ (isExpression ^ isCustomBean)))) {
- parserContext.getReaderContext().error(
- "Exactly one of the 'ref', 'value', 'expression' or inner bean is required.", element);
- }
- BeanDefinitionBuilder valueProcessorBuilder = null;
- if (isValue) {
- if (hasMethod) {
- parserContext.getReaderContext().error(
- "The 'method' attribute cannot be used with the 'value' attribute.", element);
- }
- Object headerValue = (headerType != null) ?
- new TypedStringValue(value, headerType) : value;
- valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
- valueProcessorBuilder.addConstructorArgValue(headerValue);
- }
- else if (isExpression) {
- if (hasMethod) {
- parserContext.getReaderContext().error(
- "The 'method' attribute cannot be used with the 'expression' attribute.", element);
- }
- valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor");
- if (expressionElement != null) {
- BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DynamicExpression.class);
- dynamicExpressionBuilder.addConstructorArgValue(expressionElement.getAttribute("key"));
- dynamicExpressionBuilder.addConstructorArgReference(expressionElement.getAttribute("source"));
- valueProcessorBuilder.addConstructorArgValue(dynamicExpressionBuilder.getBeanDefinition());
- }
- else {
- valueProcessorBuilder.addConstructorArgValue(expression);
- }
- valueProcessorBuilder.addConstructorArgValue(headerType);
- }
- else if (isCustomBean) {
- if (StringUtils.hasText(headerElement.getAttribute("type"))) {
- parserContext.getReaderContext().error(
- "The 'type' attribute cannot be used with an inner bean.", element);
- }
- if (hasMethod || isScript) {
- valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
- valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
- if (hasMethod) {
- valueProcessorBuilder.addConstructorArgValue(method);
- }
- }
- else {
- valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
- valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
- }
- }
- else {
- if (StringUtils.hasText(headerElement.getAttribute("type"))) {
- parserContext.getReaderContext().error(
- "The 'type' attribute cannot be used with the 'ref' attribute.", element);
- }
- if (hasMethod) {
- valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
- valueProcessorBuilder.addConstructorArgReference(ref);
- valueProcessorBuilder.addConstructorArgValue(method);
- }
- else {
- valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
- valueProcessorBuilder.addConstructorArgReference(ref);
- }
- }
- IntegrationNamespaceUtils.setValueIfAttributeDefined(valueProcessorBuilder, headerElement, "overwrite");
- headers.put(headerName, valueProcessorBuilder.getBeanDefinition());
+ }
+ else {
+ this.addHeader(element, headers, parserContext, headerName, headerElement, headerType, expression,
+ overwrite);
}
}
}
}
+ private void addHeader(Element element, ManagedMap headers, ParserContext parserContext,
+ String headerName, Element headerElement, Class> headerType, String expression, String overwrite) {
+
+ String value = headerElement.getAttribute("value");
+ String ref = headerElement.getAttribute(REF_ATTRIBUTE);
+ String method = headerElement.getAttribute(METHOD_ATTRIBUTE);
+ if (expression == null) {
+ expression = headerElement.getAttribute(EXPRESSION_ATTRIBUTE);
+ }
+
+ Element beanElement = null;
+ Element scriptElement = null;
+ Element expressionElement = null;
+
+ List subElements = DomUtils.getChildElements(headerElement);
+ if (!subElements.isEmpty()) {
+ Element subElement = subElements.get(0);
+ String subElementLocalName = subElement.getLocalName();
+ if ("bean".equals(subElementLocalName)) {
+ beanElement = subElement;
+ }
+ else if ("script".equals(subElementLocalName)) {
+ scriptElement = subElement;
+ }
+ else if ("expression".equals(subElementLocalName)) {
+ expressionElement = subElement;
+ }
+ if (beanElement == null && scriptElement == null && expressionElement == null) {
+ parserContext.getReaderContext().error("Only 'bean', 'script' or 'expression' can be defined as a sub-element", element);
+ }
+ }
+ if (StringUtils.hasText(expression) && expressionElement != null) {
+ parserContext.getReaderContext().error("The 'expression' attribute and sub-element are mutually exclusive", element);
+ }
+
+ boolean isValue = StringUtils.hasText(value);
+ boolean isRef = StringUtils.hasText(ref);
+ boolean hasMethod = StringUtils.hasText(method);
+ boolean isExpression = StringUtils.hasText(expression) || expressionElement != null;
+ boolean isScript = scriptElement != null;
+
+ BeanDefinition innerComponentDefinition = null;
+
+ if (beanElement != null) {
+ innerComponentDefinition = parserContext.getDelegate().parseBeanDefinitionElement(beanElement).getBeanDefinition();
+ }
+ else if (isScript) {
+ innerComponentDefinition = parserContext.getDelegate().parseCustomElement(scriptElement);
+ }
+
+ boolean isCustomBean = innerComponentDefinition != null;
+
+ if (hasMethod && isScript) {
+ parserContext.getReaderContext().error("The 'method' attribute cannot be used when a 'script' sub-element is defined", element);
+ }
+
+ if (!(isValue ^ (isRef ^ (isExpression ^ isCustomBean)))) {
+ parserContext.getReaderContext().error(
+ "Exactly one of the 'ref', 'value', 'expression' or inner bean is required.", element);
+ }
+ BeanDefinitionBuilder valueProcessorBuilder = null;
+ if (isValue) {
+ if (hasMethod) {
+ parserContext.getReaderContext().error(
+ "The 'method' attribute cannot be used with the 'value' attribute.", element);
+ }
+ Object headerValue = (headerType != null) ?
+ new TypedStringValue(value, headerType) : value;
+ valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
+ IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
+ valueProcessorBuilder.addConstructorArgValue(headerValue);
+ }
+ else if (isExpression) {
+ if (hasMethod) {
+ parserContext.getReaderContext().error(
+ "The 'method' attribute cannot be used with the 'expression' attribute.", element);
+ }
+ valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
+ IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor");
+ if (expressionElement != null) {
+ BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DynamicExpression.class);
+ dynamicExpressionBuilder.addConstructorArgValue(expressionElement.getAttribute("key"));
+ dynamicExpressionBuilder.addConstructorArgReference(expressionElement.getAttribute("source"));
+ valueProcessorBuilder.addConstructorArgValue(dynamicExpressionBuilder.getBeanDefinition());
+ }
+ else {
+ valueProcessorBuilder.addConstructorArgValue(expression);
+ }
+ valueProcessorBuilder.addConstructorArgValue(headerType);
+ }
+ else if (isCustomBean) {
+ if (StringUtils.hasText(headerElement.getAttribute("type"))) {
+ parserContext.getReaderContext().error(
+ "The 'type' attribute cannot be used with an inner bean.", element);
+ }
+ if (hasMethod || isScript) {
+ valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
+ IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
+ valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
+ if (hasMethod) {
+ valueProcessorBuilder.addConstructorArgValue(method);
+ }
+ }
+ else {
+ valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
+ IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
+ valueProcessorBuilder.addConstructorArgValue(innerComponentDefinition);
+ }
+ }
+ else {
+ if (StringUtils.hasText(headerElement.getAttribute("type"))) {
+ parserContext.getReaderContext().error(
+ "The 'type' attribute cannot be used with the 'ref' attribute.", element);
+ }
+ if (hasMethod) {
+ valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
+ IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.MessageProcessingHeaderValueMessageProcessor");
+ valueProcessorBuilder.addConstructorArgReference(ref);
+ valueProcessorBuilder.addConstructorArgValue(method);
+ }
+ else {
+ valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
+ IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.support.StaticHeaderValueMessageProcessor");
+ valueProcessorBuilder.addConstructorArgReference(ref);
+ }
+ }
+ if (StringUtils.hasText(overwrite)) {
+ valueProcessorBuilder.addPropertyValue("overwrite", overwrite);
+ }
+ headers.put(headerName, valueProcessorBuilder.getBeanDefinition());
+ }
+
/**
* Subclasses may override this method to provide any additional processing.
*/
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 381a8a896f..d5ad54e417 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
@@ -16,6 +16,8 @@
package org.springframework.integration.context;
+import java.util.Properties;
+
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -45,6 +47,12 @@ public abstract class IntegrationContextUtils {
public static final String INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME = "integrationEvaluationContext";
+ public static final String INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME = "integrationHeaderChannelRegistry";
+
+ public static final String INTEGRATION_PROPERTIES_BEAN_NAME = "integrationProperties";
+
+ private static final Properties EMPTY_PROPERTIES = new Properties();
+
/**
* Return the {@link MetadataStore} bean whose name is "metadataStore".
* @param beanFactory BeanFactory for lookup, must not be null.
@@ -104,4 +112,24 @@ public abstract class IntegrationContextUtils {
return beanFactory.getBean(beanName, type);
}
+ /**
+ * @return the global {@link IntegrationContextUtils#INTEGRATION_PROPERTIES_BEAN_NAME}
+ * bean from provided {@code #beanFactory}, which represents the merged
+ * properties values from all 'META-INF/spring.integration.default.properties'
+ * and 'META-INF/spring.integration.properties'.
+ * May return {@link IntegrationContextUtils#EMPTY_PROPERTIES} if there is no
+ * {@link IntegrationContextUtils#INTEGRATION_PROPERTIES_BEAN_NAME} bean within
+ * provided {@code #beanFactory} or provided {@code #beanFactory} is null.
+ */
+ public static Properties getIntegrationProperties(BeanFactory beanFactory) {
+ Properties properties = null;
+ if (beanFactory != null) {
+ properties = getBeanOfType(beanFactory, INTEGRATION_PROPERTIES_BEAN_NAME, Properties.class);
+ }
+ if (properties == null) {
+ properties = EMPTY_PROPERTIES;
+ }
+ return properties;
+ }
+
}
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 f3f7756e60..18a3051927 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
@@ -16,6 +16,8 @@
package org.springframework.integration.context;
+import java.util.Properties;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -47,6 +49,7 @@ import org.springframework.util.StringUtils;
* @author Josh Long
* @author Stefan Ferstl
* @author Gary Russell
+ * @author Artem Bilan
*/
public abstract class IntegrationObjectSupport implements BeanNameAware, NamedComponent,
ApplicationContextAware, BeanFactoryAware, InitializingBean {
@@ -169,6 +172,13 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo
return this.applicationContext == null ? null : this.applicationContext.getId();
}
+ /**
+ * @see IntegrationContextUtils#getIntegrationProperties
+ */
+ protected Properties getIntegrationProperties() {
+ return IntegrationContextUtils.getIntegrationProperties(this.beanFactory);
+ }
+
@Override
public String toString() {
return (this.beanName != null) ? this.beanName : super.toString();
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java
new file mode 100644
index 0000000000..c54cb0dae3
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationProperties.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.integration.context;
+
+/**
+ * Convention Enumeration to represent keys from 'META-INF/spring.integration.properties'.
+ *
+ * @author Artem Bilan
+ * @since 3.0
+ */
+public interface IntegrationProperties {
+
+ String LATE_REPLY_LOGGING_LEVEL = "messagingTemplate.lateReply.logging.level";
+
+ String CHANNELS_AUTOCREATE = "channels.autoCreate";
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java
index 1e726543a6..5777708b7c 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionEvalMap.java
@@ -23,10 +23,13 @@ import java.util.Set;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
+import org.springframework.expression.common.LiteralExpression;
+import org.springframework.util.Assert;
/**
*
- * An immutable {@link AbstractMap} implementation that wraps a Map
+ * An immutable {@link AbstractMap} implementation that wraps a {@code Map},
+ * where values must be instances of {@link String} or {@link Expression},
* and evaluates an {@code expression} for the provided {@code key} from the underlying
* {@code original} Map.
*
@@ -38,16 +41,18 @@ import org.springframework.expression.Expression;
*
* A {@link ExpressionEvalMapBuilder} must be used to instantiate this class
* via its {@link #from(Map)} method:
+ *
* {@code
- * ExpressionEvalMap evalMap = ExpressionEvalMap
- * .from(expressions)
- .usingCallback(new EvaluationCallback() {
- Object evaluate(Expression expression) {
- // return some expression evaluation
- }
- })
- .build();
- * }
+ *ExpressionEvalMap evalMap = ExpressionEvalMap
+ * .from(expressions)
+ * .usingCallback(new EvaluationCallback() {
+ * Object evaluate(Expression expression) {
+ * // return some expression evaluation
+ * }
+ * })
+ * .build();
+ *}
+ *
*
*
* Thread-safety depends on the original underlying Map.
@@ -68,11 +73,11 @@ public final class ExpressionEvalMap extends AbstractMap {
};
- private final Map original;
+ private final Map original;
private final EvaluationCallback evaluationCallback;
- private ExpressionEvalMap(Map original, EvaluationCallback evaluationCallback) {
+ private ExpressionEvalMap(Map original, EvaluationCallback evaluationCallback) {
this.original = original;
this.evaluationCallback = evaluationCallback;
}
@@ -83,8 +88,20 @@ public final class ExpressionEvalMap extends AbstractMap {
*/
@Override
public Object get(Object key) {
- Expression expression = original.get(key);
- if (expression != null) {
+ Object value = original.get(key);
+ if (value != null) {
+ Expression expression;
+ if (value instanceof Expression) {
+ expression = (Expression) value;
+ }
+ else if (value instanceof String) {
+ expression = new LiteralExpression((String) value);
+ }
+ else {
+ throw new IllegalArgumentException("Values must be "
+ + "'java.lang.String' or 'org.springframework.expression.Expression'; the value type for key "
+ + key + " is : " + value.getClass());
+ }
return this.evaluationCallback.evaluate(expression);
}
return null;
@@ -136,7 +153,7 @@ public final class ExpressionEvalMap extends AbstractMap {
}
@Override
- public void putAll(Map extends String, ? extends Object> m) {
+ public void putAll(Map extends String, ?> m) {
throw new UnsupportedOperationException();
}
@@ -155,7 +172,13 @@ public final class ExpressionEvalMap extends AbstractMap {
throw new UnsupportedOperationException();
}
- public static ExpressionEvalMapBuilder from(Map expressions) {
+ @Override
+ public String toString() {
+ return this.original.toString();
+ }
+
+ public static ExpressionEvalMapBuilder from(Map expressions) {
+ Assert.notNull(expressions, "'expressions' must not be null.");
return new ExpressionEvalMapBuilder(expressions);
}
@@ -205,7 +228,7 @@ public final class ExpressionEvalMap extends AbstractMap {
*/
public static final class ExpressionEvalMapBuilder {
- private final Map expressions;
+ private final Map expressions;
private EvaluationCallback evaluationCallback;
@@ -219,7 +242,7 @@ public final class ExpressionEvalMap extends AbstractMap {
private final ExpressionEvalMapFinalBuilder finalBuilder = new ExpressionEvalMapFinalBuilderImpl();
- private ExpressionEvalMapBuilder(Map expressions) {
+ private ExpressionEvalMapBuilder(Map expressions) {
this.expressions = expressions;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
index 256953d59b..4c9d2bbcbf 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@ import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.converter.SimpleMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
-import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
@@ -43,6 +42,7 @@ import org.springframework.util.Assert;
* well as the timeout values for sending and receiving Messages.
*
* @author Mark Fisher
+ * @author Gary Russell
*/
public abstract class MessagingGatewaySupport extends AbstractEndpoint implements TrackableComponent {
@@ -153,6 +153,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint implement
* Specify whether this gateway should be tracked in the Message History
* of Messages that originate from its send or sendAndReceive operations.
*/
+ @Override
public void setShouldTrack(boolean shouldTrack) {
this.historyWritingPostProcessor.setShouldTrack(shouldTrack);
}
@@ -283,7 +284,11 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint implement
return;
}
AbstractEndpoint correlator = null;
- MessageHandler handler = new BridgeHandler();
+ BridgeHandler handler = new BridgeHandler();
+ if (this.getBeanFactory() != null) {
+ handler.setBeanFactory(this.getBeanFactory());
+ }
+ handler.afterPropertiesSet();
if (this.replyChannel instanceof SubscribableChannel) {
correlator = new EventDrivenConsumer(
(SubscribableChannel) this.replyChannel, handler);
@@ -320,6 +325,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint implement
private static class DefaultRequestMapper implements InboundMessageMapper {
+ @Override
public Message> toMessage(Object object) throws Exception {
if (object instanceof Message>) {
return (Message>) object;
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStore.java b/spring-integration-core/src/main/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStore.java
index 26c35231ec..b6599cef0d 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStore.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/metadata/PropertiesPersistingMetadataStore.java
@@ -28,6 +28,7 @@ import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
@@ -62,6 +63,7 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
this.baseDirectory = baseDirectory;
}
+ @Override
public void afterPropertiesSet() throws Exception {
File baseDir = new File(baseDirectory);
baseDir.mkdirs();
@@ -78,20 +80,22 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
this.loadMetadata();
}
+ @Override
public void put(String key, String value) {
this.metadata.setProperty(key, value);
}
+ @Override
public String get(String key) {
return this.metadata.getProperty(key);
}
@Override
- @SuppressWarnings("uchecked")
public String remove(String key) {
return (String) this.metadata.remove(key);
}
+ @Override
public void destroy() throws Exception {
this.saveMetadata();
}
@@ -100,12 +104,12 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
OutputStream outputStream = null;
try {
outputStream = new BufferedOutputStream(new FileOutputStream(this.file));
- this.persister.store(this.metadata, outputStream, "Last feed entry");
+ this.persister.store(this.metadata, outputStream, "Last 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);
+ logger.warn("Failed to persist entry. This may result in a duplicate "
+ + "entry after this component is restarted.", e);
}
finally {
try {
@@ -128,8 +132,8 @@ public class PropertiesPersistingMetadataStore implements MetadataStore, Initial
}
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);
+ logger.warn("Failed to load entry from the persistent store. This may result in a duplicate " +
+ "entry after this component is restarted", e);
}
finally {
try {
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/channel/BeanFactoryChannelResolver.java b/spring-integration-core/src/main/java/org/springframework/integration/support/channel/BeanFactoryChannelResolver.java
new file mode 100644
index 0000000000..c2164b5da6
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/support/channel/BeanFactoryChannelResolver.java
@@ -0,0 +1,109 @@
+/*
+ * 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.support.channel;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.integration.context.IntegrationContextUtils;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.core.DestinationResolutionException;
+import org.springframework.messaging.core.DestinationResolver;
+import org.springframework.util.Assert;
+
+/**
+ * {@link ChannelResolver} implementation based on a Spring {@link BeanFactory}.
+ *
+ * Will lookup Spring managed beans identified by bean name,
+ * expecting them to be of type {@link MessageChannel}.
+ *
+ * @author Mark Fisher
+ * @see org.springframework.beans.factory.BeanFactory
+ */
+public class BeanFactoryChannelResolver implements DestinationResolver, BeanFactoryAware {
+
+ private final static Log logger = LogFactory.getLog(BeanFactoryChannelResolver.class);
+
+ private volatile BeanFactory beanFactory;
+
+ private volatile HeaderChannelRegistry replyChannelRegistry;
+
+ /**
+ * Create a new instance of the {@link BeanFactoryChannelResolver} class.
+ * The BeanFactory to access must be set via setBeanFactory.
+ * This will happen automatically if this resolver is defined within an
+ * ApplicationContext thereby receiving the callback upon initialization.
+ * @see #setBeanFactory
+ */
+ public BeanFactoryChannelResolver() {
+ }
+
+ /**
+ * Create a new instance of the {@link BeanFactoryChannelResolver} class.
+ *
Use of this constructor is redundant if this object is being created
+ * by a Spring IoC container as the supplied {@link BeanFactory} will be
+ * replaced by the {@link BeanFactory} that creates it (c.f. the
+ * {@link BeanFactoryAware} contract). So only use this constructor if you
+ * are instantiating this object explicitly rather than defining a bean.
+ *
+ * @param beanFactory the bean factory to be used to lookup {@link MessageChannel}s.
+ */
+ public BeanFactoryChannelResolver(BeanFactory beanFactory) {
+ Assert.notNull(beanFactory, "BeanFactory must not be null");
+ this.lookupHeaderChannelRegistry(beanFactory);
+ }
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ this.lookupHeaderChannelRegistry(beanFactory);
+ }
+
+ private void lookupHeaderChannelRegistry(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ try {
+ this.replyChannelRegistry = beanFactory.getBean(
+ IntegrationContextUtils.INTEGRATION_HEADER_CHANNEL_REGISTRY_BEAN_NAME,
+ HeaderChannelRegistry.class);
+ }
+ catch (Exception e) {
+ logger.warn("No HeaderChannelRegistry found", e);
+ }
+ }
+
+ @Override
+ public MessageChannel resolveDestination(String name) {
+ Assert.state(this.beanFactory != null, "BeanFactory is required");
+ try {
+ return this.beanFactory.getBean(name, MessageChannel.class);
+ }
+ catch (BeansException e) {
+ if (this.replyChannelRegistry != null) {
+ MessageChannel channel = this.replyChannelRegistry.channelNameToChannel(name);
+ if (channel != null) {
+ return channel;
+ }
+ }
+ throw new DestinationResolutionException(
+ "failed to look up MessageChannel bean with name '" + name + "'", e);
+ }
+ }
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/channel/HeaderChannelRegistry.java b/spring-integration-core/src/main/java/org/springframework/integration/support/channel/HeaderChannelRegistry.java
new file mode 100644
index 0000000000..6e0629c2fb
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/support/channel/HeaderChannelRegistry.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.support.channel;
+
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedOperation;
+import org.springframework.messaging.MessageChannel;
+
+/**
+ * Implementations convert a channel to a name, retaining a reference to the channel keyed by the name.
+ * Allows a downstream {@link BeanFactoryChannelResolver} to find the channel by name in
+ * the event that the flow serialized the message at some point.
+ *
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public interface HeaderChannelRegistry {
+
+ /**
+ * Converts the channel to a name (String). If the channel is not a
+ * {@link MessageChannel}, it is returned unchanged.
+ *
+ * @param channel The channel.
+ * @return The channel name, or the channel if it is not a MessageChannel.
+ */
+ public abstract Object channelToChannelName(Object channel);
+
+ /**
+ * Converts the channel name back to a {@link MessageChannel} (if it is
+ * registered).
+ * @param name The name of the channel.
+ * @return The channel, or null if there is no channel registered with the name.
+ */
+ public abstract MessageChannel channelNameToChannel(String name);
+
+ /**
+ * @return the current size of the registry
+ */
+ @ManagedAttribute
+ public abstract int size();
+
+ /**
+ * Cancel the scheduled reap task and run immediately; then reschedule.
+ */
+ @ManagedOperation(description = "Cancel the scheduled reap task and run immediately; then reschedule.")
+ public abstract void runReaper();
+
+}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java
index c3f87d4628..dac3be6142 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ContentEnricher.java
@@ -214,6 +214,10 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
this.gateway.setReplyChannel(replyChannel);
}
+ if (this.getBeanFactory() != null) {
+ this.gateway.setBeanFactory(this.getBeanFactory());
+ }
+
this.gateway.afterPropertiesSet();
}
@@ -294,6 +298,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
* Lifecycle implementation. If no requestChannel is defined, this method
* has no effect as in that case no Gateway is initialized.
*/
+ @Override
public void start() {
if (this.gateway != null) {
this.gateway.start();
@@ -304,6 +309,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
* Lifecycle implementation. If no requestChannel is defined, this method
* has no effect as in that case no Gateway is initialized.
*/
+ @Override
public void stop() {
if (this.gateway != null) {
this.gateway.stop();
@@ -314,6 +320,7 @@ public class ContentEnricher extends AbstractReplyProducingMessageHandler implem
* Lifecycle implementation. If no requestChannel is defined, this method
* will return always return true as no Gateway is initialized.
*/
+ @Override
public boolean isRunning() {
if (this.gateway != null) {
return this.gateway.isRunning();
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
index a8eef22920..a43593183e 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
@@ -28,6 +28,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -49,15 +50,16 @@ import org.springframework.expression.Expression;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
-import org.springframework.messaging.Message;
import org.springframework.integration.MessageHandlingException;
-import org.springframework.messaging.MessagingException;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Headers;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.annotation.Payloads;
import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.ReflectionUtils.MethodFilter;
@@ -76,21 +78,31 @@ import org.springframework.util.StringUtils;
* @author Gunnar Hillert
* @author Soby Chacko
* @author Gary Russell
+ * @author Artem Bilan
*
* @since 2.0
*/
public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator {
+ private static final String CANDIDATE_METHODS = "CANDIDATE_METHODS";
+
+ private static final String CANDIDATE_MESSAGE_METHODS = "CANDIDATE_MESSAGE_METHODS";
+
private final Log logger = LogFactory.getLog(this.getClass());
private final Object targetObject;
-
private volatile String displayString;
private volatile boolean requiresReply;
private final Map, HandlerMethod> handlerMethods;
+ private final Map, HandlerMethod> handlerMessageMethods;
+
+ private final LinkedList, HandlerMethod>> handlerMethodsList;
+
+ private final HandlerMethod handlerMethod;
+
private final Class> expectedType;
private final boolean canProcessMessageList;
@@ -154,11 +166,12 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
Assert.isTrue(method.getReturnType() != Void.class && method.getReturnType() != Void.TYPE,
"method must have a return type");
}
- HandlerMethod handlerMethod = new HandlerMethod(method, canProcessMessageList);
Assert.notNull(targetObject, "targetObject must not be null");
this.targetObject = targetObject;
- this.handlerMethods = Collections., HandlerMethod> singletonMap(handlerMethod.getTargetParameterType()
- .getObjectType(), handlerMethod);
+ this.handlerMethod = new HandlerMethod(method, canProcessMessageList);
+ this.handlerMethods = null;
+ this.handlerMessageMethods = null;
+ this.handlerMethodsList = null;
this.prepareEvaluationContext(this.getEvaluationContext(false), method, annotationType);
this.setDisplayString(targetObject, method);
}
@@ -170,7 +183,32 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
this.expectedType = expectedType;
this.targetObject = targetObject;
this.requiresReply = expectedType != null;
- this.handlerMethods = this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply);
+ Map, HandlerMethod>> handlerMethodsForTarget =
+ this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply);
+ Map, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS);
+ Map, HandlerMethod> handlerMessageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS);
+ if ((handlerMethods.size() == 1 && handlerMessageMethods.isEmpty()) ||
+ (handlerMessageMethods.size() == 1 && handlerMethods.isEmpty())) {
+ if (handlerMethods.size() == 1) {
+ this.handlerMethod = handlerMethods.values().iterator().next();
+ }
+ else {
+ this.handlerMethod = handlerMessageMethods.values().iterator().next();
+ }
+ this.handlerMethods = null;
+ this.handlerMessageMethods = null;
+ this.handlerMethodsList = null;
+ }
+ else {
+ this.handlerMethod = null;
+ this.handlerMethods = handlerMethods;
+ this.handlerMessageMethods = handlerMessageMethods;
+ this.handlerMethodsList = new LinkedList, HandlerMethod>>();
+
+ //TODO Consider to use global option to determine a precedence of methods
+ this.handlerMethodsList.add(this.handlerMethods);
+ this.handlerMethodsList.add(this.handlerMessageMethods);
+ }
this.prepareEvaluationContext(this.getEvaluationContext(false), methodName, annotationType);
this.setDisplayString(targetObject, methodName);
}
@@ -181,7 +219,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
sb.append("." + ((Method) targetMethod).getName());
}
else if (targetMethod instanceof String) {
- sb.append("." + (String) targetMethod);
+ sb.append("." + targetMethod);
}
this.displayString = sb.toString() + "]";
}
@@ -192,7 +230,8 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
if (method instanceof Method) {
context.registerMethodFilter(targetType, new FixedMethodFilter((Method) method));
if (expectedType != null) {
- Assert.state(context.getTypeConverter().canConvert(TypeDescriptor.valueOf(((Method) method).getReturnType()), TypeDescriptor.valueOf(expectedType)),
+ Assert.state(context.getTypeConverter().canConvert(TypeDescriptor.valueOf(((Method) method).getReturnType()),
+ TypeDescriptor.valueOf(expectedType)),
"Cannot convert to expected type (" + expectedType + ") from " + method);
}
}
@@ -220,64 +259,48 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
}
private T processInternal(ParametersWrapper parameters) throws Exception {
- Throwable evaluationException = null;
- List candidates = this.findHandlerMethodsForParameters(parameters);
- Assert.state(!candidates.isEmpty(), "No candidate methods found for messages.");
- for (HandlerMethod candidate : candidates) {
- try {
- Expression expression = candidate.getExpression();
- Class> expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType();
- @SuppressWarnings("unchecked")
- T result = (T) this.evaluateExpression(expression, parameters, expectedType);
- if (this.requiresReply) {
- Assert.notNull(result,
- "Expression evaluation result was null, but this processor requires a reply.");
- }
- return result;
- }
- // keep the first exception
- catch (EvaluationException e) {
- if (evaluationException == null) {
- evaluationException = e.getCause();
- }
- if (evaluationException == null) {
- evaluationException = e;
- }
- }
- catch (MessageHandlingException e) {
- if (evaluationException == null) {
- evaluationException = e.getCause();
- }
- if (evaluationException == null) {
- evaluationException = e;
- }
- }
- catch (Exception e) {
- if (evaluationException == null) {
- evaluationException = e;
- }
+ HandlerMethod candidate = this.findHandlerMethodForParameters(parameters);
+ Assert.notNull(candidate, "No candidate methods found for messages.");
+ Expression expression = candidate.getExpression();
+ Class> expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType();
+ try {
+ @SuppressWarnings("unchecked")
+ T result = (T) this.evaluateExpression(expression, parameters, expectedType);
+ if (this.requiresReply) {
+ Assert.notNull(result,
+ "Expression evaluation result was null, but this processor requires a reply.");
}
+ return result;
}
- if (evaluationException instanceof Exception) {
- throw (Exception) evaluationException;
- }
- else if (evaluationException instanceof Error) {
- throw (Error) evaluationException;
- }
- else {
- throw new IllegalStateException("Cannot process message", evaluationException);
+ catch (Exception e) {
+ Throwable evaluationException = e;
+ if ((e instanceof EvaluationException || e instanceof MessageHandlingException) && e.getCause() != null) {
+ evaluationException = e.getCause();
+ }
+ if (evaluationException instanceof Exception) {
+ throw (Exception) evaluationException;
+ }
+ else {
+ throw new IllegalStateException("Cannot process message", evaluationException);
+ }
}
}
- private Map, HandlerMethod> findHandlerMethodsForTarget(final Object targetObject,
+ private Map, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject,
final Class extends Annotation> annotationType, final String methodName, final boolean requiresReply) {
+ Map, HandlerMethod>> handlerMethods = new HashMap, HandlerMethod>>();
+
final Map, HandlerMethod> candidateMethods = new HashMap, HandlerMethod>();
+ final Map, HandlerMethod> candidateMessageMethods = new HashMap, HandlerMethod>();
final Map, HandlerMethod> fallbackMethods = new HashMap, HandlerMethod>();
+ final Map, HandlerMethod> fallbackMessageMethods = new HashMap, HandlerMethod>();
final AtomicReference> ambiguousFallbackType = new AtomicReference>();
+ final AtomicReference> ambiguousFallbackMessageGenericType = new AtomicReference>();
final Class> targetClass = this.getTargetClass(targetObject);
MethodFilter methodFilter = new UniqueMethodFilter(targetClass);
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
+ @Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
boolean matchesAnnotation = false;
if (method.isBridge()) {
@@ -311,37 +334,75 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
}
return;
}
- Class> targetParameterType = handlerMethod.getTargetParameterType().getObjectType();
+ Class> targetParameterType = handlerMethod.getTargetParameterType();
if (matchesAnnotation || annotationType == null) {
- Assert.isTrue(!candidateMethods.containsKey(targetParameterType),
- "Found more than one method match for type [" + targetParameterType + "]");
- candidateMethods.put(targetParameterType, handlerMethod);
+ if (handlerMethod.isMessageMethod()) {
+ if (candidateMessageMethods.containsKey(targetParameterType)) {
+ throw new IllegalArgumentException("Found more than one method match for type " +
+ "[Message<" + targetParameterType + ">]");
+ }
+ candidateMessageMethods.put(targetParameterType, handlerMethod);
+ }
+ else {
+ if (candidateMethods.containsKey(targetParameterType)) {
+ String exceptionMessage = "Found more than one method match for ";
+ if (Void.class.equals(targetParameterType)) {
+ exceptionMessage += "empty parameter for 'payload'";
+ }
+ else {
+ exceptionMessage += "type [" + targetParameterType + "]";
+ }
+ throw new IllegalArgumentException(exceptionMessage);
+ }
+ candidateMethods.put(targetParameterType, handlerMethod);
+ }
}
else {
- if (fallbackMethods.containsKey(targetParameterType)) {
- // we need to check for duplicate type matches,
- // but only if we end up falling back
- // and we'll only keep track of the first one
- ambiguousFallbackType.compareAndSet(null, targetParameterType);
+ if (handlerMethod.isMessageMethod()) {
+ if (fallbackMessageMethods.containsKey(targetParameterType)) {
+ // we need to check for duplicate type matches,
+ // but only if we end up falling back
+ // and we'll only keep track of the first one
+ ambiguousFallbackMessageGenericType.compareAndSet(null, targetParameterType);
+ }
+ fallbackMessageMethods.put(targetParameterType, handlerMethod);
+ }
+ else {
+ if (fallbackMethods.containsKey(targetParameterType)) {
+ // we need to check for duplicate type matches,
+ // but only if we end up falling back
+ // and we'll only keep track of the first one
+ ambiguousFallbackType.compareAndSet(null, targetParameterType);
+ }
+ fallbackMethods.put(targetParameterType, handlerMethod);
}
- fallbackMethods.put(targetParameterType, handlerMethod);
}
}
}, methodFilter);
- if (!candidateMethods.isEmpty()) {
- return candidateMethods;
+
+ if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) {
+ handlerMethods.put(CANDIDATE_METHODS, candidateMethods);
+ handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods);
+ return handlerMethods;
}
- if ((fallbackMethods.isEmpty() || ambiguousFallbackType.get() != null) && ServiceActivator.class.equals(annotationType)) {
- // a Service Activator can fallback to either MessageHandler.handleMessage(m) or RequestReplyExchanger.exchange(m)
+ if ((ambiguousFallbackType.get() != null
+ || ambiguousFallbackMessageGenericType.get() != null)
+ && ServiceActivator.class.equals(annotationType)) {
+ /*
+ * When there are ambiguous fallback methods,
+ * a Service Activator can finally fallback to RequestReplyExchanger.exchange(m).
+ * Ambiguous means > 1 method that takes the same payload type, or > 1 method
+ * that takes a Message with the same generic type.
+ */
List frameworkMethods = new ArrayList();
Class>[] allInterfaces = org.springframework.util.ClassUtils.getAllInterfacesForClass(targetClass);
for (Class> iface : allInterfaces) {
try {
if ("org.springframework.integration.gateway.RequestReplyExchanger".equals(iface.getName())) {
frameworkMethods.add(targetClass.getMethod("exchange", Message.class));
- }
- else if ("org.springframework.messaging.MessageHandler".equals(iface.getName()) && !requiresReply) {
- frameworkMethods.add(targetClass.getMethod("handleMessage", Message.class));
+ if (logger.isDebugEnabled()) {
+ logger.debug(targetObject.getClass() + ": Ambiguous fallback methods; using RequestReplyExchanger.exchange()");
+ }
}
}
catch (Exception e) {
@@ -350,14 +411,30 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
}
if (frameworkMethods.size() == 1) {
HandlerMethod handlerMethod = new HandlerMethod(frameworkMethods.get(0), canProcessMessageList);
- return Collections., HandlerMethod>singletonMap(Object.class, handlerMethod);
+ handlerMethods.put(CANDIDATE_METHODS, Collections., HandlerMethod>singletonMap(Object.class, handlerMethod));
+ handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods);
+ return handlerMethods;
}
}
- Assert.notEmpty(fallbackMethods, "Target object of type [" + this.targetObject.getClass()
- + "] has no eligible methods for handling Messages.");
+
+ try {
+ Assert.state(!fallbackMethods.isEmpty() || !fallbackMessageMethods.isEmpty(),
+ "Target object of type [" + this.targetObject.getClass() + "] has no eligible methods for handling Messages.");
+ }
+ catch (Exception e) {
+ //TODO backward compatibility
+ throw new IllegalArgumentException(e.getMessage());
+ }
+
Assert.isNull(ambiguousFallbackType.get(), "Found ambiguous parameter type [" + ambiguousFallbackType
+ "] for method match: " + fallbackMethods.values());
- return fallbackMethods;
+ Assert.isNull(ambiguousFallbackMessageGenericType.get(),
+ "Found ambiguous parameter type [" + ambiguousFallbackMessageGenericType + "] for method match: "
+ + fallbackMethods.values());
+
+ handlerMethods.put(CANDIDATE_METHODS, fallbackMethods);
+ handlerMethods.put(CANDIDATE_MESSAGE_METHODS, fallbackMessageMethods);
+ return handlerMethods;
}
private Class> getTargetClass(Object targetObject) {
@@ -388,22 +465,40 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
return targetClass;
}
- private List findHandlerMethodsForParameters(ParametersWrapper parameters) {
+ private HandlerMethod findHandlerMethodForParameters(ParametersWrapper parameters) {
+ if (this.handlerMethod != null) {
+ return this.handlerMethod;
+ }
+
final Class> payloadType = parameters.getFirstParameterType();
+
HandlerMethod closestMatch = this.findClosestMatch(payloadType);
if (closestMatch != null) {
- return Collections.singletonList(closestMatch);
+ return closestMatch;
+
}
- return new ArrayList(this.handlerMethods.values());
+
+ if (Iterable.class.isAssignableFrom(payloadType) && this.handlerMethods.containsKey(Iterator.class)) {
+ return this.handlerMethods.get(Iterator.class);
+ }
+ else {
+ return this.handlerMethods.get(Void.class);
+ }
+
}
private HandlerMethod findClosestMatch(Class> payloadType) {
- Set> candidates = this.handlerMethods.keySet();
- Class> match = null;
- if (candidates != null && !candidates.isEmpty()) {
- match = ClassUtils.findClosestMatch(payloadType, candidates, true);
+ for (Map, HandlerMethod> handlerMethods : handlerMethodsList) {
+ Set> candidates = handlerMethods.keySet();
+ Class> match = null;
+ if (!CollectionUtils.isEmpty(candidates)) {
+ match = ClassUtils.findClosestMatch(payloadType, candidates, true);
+ }
+ if (match != null) {
+ return handlerMethods.get(match);
+ }
}
- return (match != null) ? this.handlerMethods.get(match) : null;
+ return null;
}
private static boolean isMethodDefinedOnObjectClass(Method method) {
@@ -446,10 +541,13 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
private final Expression expression;
- private volatile TypeDescriptor targetParameterType;
-
private final boolean canProcessMessageList;
+ private volatile TypeDescriptor targetParameterTypeDescriptor;
+
+ private volatile Class> targetParameterType = Void.class;
+
+ private volatile boolean messageMethod;
HandlerMethod(Method method, boolean canProcessMessageList) {
this.method = method;
@@ -462,10 +560,14 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
return this.expression;
}
- TypeDescriptor getTargetParameterType() {
+ Class> getTargetParameterType() {
return this.targetParameterType;
}
+ private boolean isMessageMethod() {
+ return messageMethod;
+ }
+
@Override
public String toString() {
return this.method.toString();
@@ -476,13 +578,12 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
Class>[] parameterTypes = method.getParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
boolean hasUnqualifiedMapParameter = false;
- TypeDescriptor defaultParameterTypeDescriptor = TypeDescriptor.valueOf(List.class);
for (int i = 0; i < parameterTypes.length; i++) {
if (i != 0) {
sb.append(", ");
}
- TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(new MethodParameter(method, i));
- defaultParameterTypeDescriptor = parameterTypeDescriptor;
+ MethodParameter methodParameter = new MethodParameter(method, i);
+ TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(methodParameter);
Class> parameterType = parameterTypeDescriptor.getObjectType();
Annotation mappingAnnotation = findMappingAnnotation(parameterAnnotations[i]);
if (mappingAnnotation != null) {
@@ -494,7 +595,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
sb.append("." + qualifierExpression);
}
if (!StringUtils.hasText(qualifierExpression)) {
- this.setExclusiveTargetParameterType(parameterTypeDescriptor);
+ this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
}
if (annotationType.equals(Payloads.class)) {
@@ -505,7 +606,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
}
sb.append("]");
if (!StringUtils.hasText(qualifierExpression)) {
- this.setExclusiveTargetParameterType(parameterTypeDescriptor);
+ this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
}
else if (annotationType.equals(Headers.class)) {
@@ -515,17 +616,18 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
}
else if (annotationType.equals(Header.class)) {
Header headerAnnotation = (Header) mappingAnnotation;
- sb.append(this.determineHeaderExpression(headerAnnotation, new MethodParameter(method, i)));
+ sb.append(this.determineHeaderExpression(headerAnnotation, methodParameter));
}
}
else if (parameterTypeDescriptor.isAssignableTo(messageTypeDescriptor)) {
+ this.messageMethod = true;
sb.append("message");
- this.setExclusiveTargetParameterType(parameterTypeDescriptor);
+ this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
else if ((parameterTypeDescriptor.isAssignableTo(messageListTypeDescriptor) || parameterTypeDescriptor
.isAssignableTo(messageArrayTypeDescriptor))) {
sb.append("messages");
- this.setExclusiveTargetParameterType(parameterTypeDescriptor);
+ this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
else if (Collection.class.isAssignableFrom(parameterType) || parameterType.isArray()) {
if (canProcessMessageList) {
@@ -534,11 +636,11 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
else {
sb.append("payload");
}
- this.setExclusiveTargetParameterType(parameterTypeDescriptor);
+ this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
else if (Iterator.class.isAssignableFrom(parameterType)) {
if (canProcessMessageList) {
- Type type = method.getGenericParameterTypes()[0];
+ Type type = method.getGenericParameterTypes()[i];
Type parameterizedType = null;
if (type instanceof ParameterizedType){
parameterizedType = ((ParameterizedType)type).getActualTypeArguments()[0];
@@ -546,7 +648,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
parameterizedType = ((ParameterizedType) parameterizedType).getRawType();
}
}
- if (parameterizedType != null && Message.class.isAssignableFrom((Class>)parameterizedType)){
+ if (parameterizedType != null && Message.class.isAssignableFrom((Class>) parameterizedType)){
sb.append("messages.iterator()");
}
else {
@@ -556,7 +658,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
else {
sb.append("payload.iterator()");
}
- this.setExclusiveTargetParameterType(parameterTypeDescriptor);
+ this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
else if (Map.class.isAssignableFrom(parameterType)) {
if (Properties.class.isAssignableFrom(parameterType)) {
@@ -573,19 +675,19 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
}
else {
sb.append("payload");
- this.setExclusiveTargetParameterType(parameterTypeDescriptor);
+ this.setExclusiveTargetParameterType(parameterTypeDescriptor, methodParameter);
}
}
if (hasUnqualifiedMapParameter) {
- if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType.getObjectType())) {
+ if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType)) {
throw new IllegalArgumentException(
"Unable to determine payload matching parameter due to ambiguous Map typed parameters. "
+ "Consider adding the @Payload and or @Headers annotations as appropriate.");
}
}
sb.append(")");
- if (this.targetParameterType == null) {
- this.targetParameterType = defaultParameterTypeDescriptor;
+ if (this.targetParameterTypeDescriptor == null) {
+ this.targetParameterTypeDescriptor = TypeDescriptor.valueOf(Void.class);
}
return EXPRESSION_PARSER.parseExpression(sb.toString());
}
@@ -638,15 +740,22 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression;
}
- private synchronized void setExclusiveTargetParameterType(TypeDescriptor targetParameterType) {
- Assert.isNull(this.targetParameterType, "Found more than one parameter type candidate: ["
- + this.targetParameterType + "] and [" + targetParameterType + "]");
- this.targetParameterType = targetParameterType;
+ private synchronized void setExclusiveTargetParameterType(TypeDescriptor targetParameterType, MethodParameter methodParameter) {
+ Assert.isNull(this.targetParameterTypeDescriptor, "Found more than one parameter type candidate: ["
+ + this.targetParameterTypeDescriptor + "] and [" + targetParameterType + "]");
+ this.targetParameterTypeDescriptor = targetParameterType;
+ if (Message.class.isAssignableFrom(targetParameterType.getObjectType())) {
+ methodParameter.increaseNestingLevel();
+ this.targetParameterType = methodParameter.getNestedParameterType();
+ methodParameter.decreaseNestingLevel();
+ }
+ else {
+ this.targetParameterType = targetParameterType.getObjectType();
+ }
}
}
- @SuppressWarnings("unused")
- private static class ParametersWrapper {
+ public class ParametersWrapper {
private final Object payload;
@@ -692,7 +801,7 @@ public class MessagingMethodInvokerHelper extends AbstractExpressionEvaluator
if (payload != null) {
return payload.getClass();
}
- return Collection.class;
+ return this.messages.getClass();
}
}
diff --git a/spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties b/spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties
new file mode 100644
index 0000000000..199b366130
--- /dev/null
+++ b/spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties
@@ -0,0 +1,2 @@
+channels.autoCreate=true
+messagingTemplate.lateReply.logging.level=warn
diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd
index 9343147740..e4b3b8f93c 100644
--- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd
+++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd
@@ -1853,6 +1853,15 @@
+
+
+
+ Converts the 'replyChannel' and 'errorChannel' headers to a String after registering it in the HeaderChannelRegistry.
+ Use this when a message is serialized for any reason. No changes are made
+ if the header does not exist, or if the header does not currently reference a MessageChannel.
+
+
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandlerTests.java
index af2bf76688..765e2d1592 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandlerTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/AbstractCorrelatingMessageHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,13 @@
package org.springframework.integration.aggregator;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -30,6 +34,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
+
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -57,6 +62,7 @@ public class AbstractCorrelatingMessageHandlerTests {
AbstractCorrelatingMessageHandler handler = new AbstractCorrelatingMessageHandler(
new MessageGroupProcessor() {
+ @Override
public Object processMessageGroup(MessageGroup group) {
return group;
}
@@ -73,6 +79,7 @@ public class AbstractCorrelatingMessageHandlerTests {
*/
Executors.newSingleThreadExecutor().execute(new Runnable() {
+ @Override
public void run() {
try {
waitReapStartLatch.await(10, TimeUnit.SECONDS);
@@ -98,6 +105,7 @@ public class AbstractCorrelatingMessageHandlerTests {
/*
* Executes when group 'bar' completes normally
*/
+ @Override
public boolean send(Message> message, long timeout) {
outputMessages.add(message);
// wake reaper
@@ -115,12 +123,14 @@ public class AbstractCorrelatingMessageHandlerTests {
return true;
}
+ @Override
public boolean send(Message> message) {
return this.send(message, 0);
}
});
handler.setReleaseStrategy(new ReleaseStrategy() {
+ @Override
public boolean canRelease(MessageGroup group) {
return group.size() == 2;
}
@@ -162,6 +172,7 @@ public class AbstractCorrelatingMessageHandlerTests {
AggregatingMessageHandler handler = new AggregatingMessageHandler(
new MessageGroupProcessor() {
+ @Override
public Object processMessageGroup(MessageGroup group) {
return group;
}
@@ -174,17 +185,20 @@ public class AbstractCorrelatingMessageHandlerTests {
/*
* Executes when group 'bar' completes normally
*/
+ @Override
public boolean send(Message> message, long timeout) {
outputMessages.add(message);
return true;
}
+ @Override
public boolean send(Message> message) {
return this.send(message, 0);
}
});
handler.setReleaseStrategy(new ReleaseStrategy() {
+ @Override
public boolean canRelease(MessageGroup group) {
return group.size() == 1;
}
@@ -208,6 +222,7 @@ public class AbstractCorrelatingMessageHandlerTests {
AggregatingMessageHandler handler = new AggregatingMessageHandler(
new MessageGroupProcessor() {
+ @Override
public Object processMessageGroup(MessageGroup group) {
return group;
}
@@ -220,17 +235,20 @@ public class AbstractCorrelatingMessageHandlerTests {
/*
* Executes when group 'bar' completes normally
*/
+ @Override
public boolean send(Message> message, long timeout) {
outputMessages.add(message);
return true;
}
+ @Override
public boolean send(Message> message) {
return this.send(message, 0);
}
});
handler.setReleaseStrategy(new ReleaseStrategy() {
+ @Override
public boolean canRelease(MessageGroup group) {
return group.size() == 1;
}
@@ -258,6 +276,7 @@ public class AbstractCorrelatingMessageHandlerTests {
MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
handler.setReleaseStrategy(new ReleaseStrategy() {
+ @Override
public boolean canRelease(MessageGroup group) {
return true;
}
@@ -283,4 +302,101 @@ public class AbstractCorrelatingMessageHandlerTests {
assertEquals(1, payload.size());
}
+ @Test /* INT-3216 */
+ public void testDontReapIfAlreadyComplete() throws Exception {
+ MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
+ AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
+ handler.setReleaseStrategy(new ReleaseStrategy() {
+
+ @Override
+ public boolean canRelease(MessageGroup group) {
+ return true;
+ }
+
+ });
+ QueueChannel outputChannel = new QueueChannel();
+ handler.setOutputChannel(outputChannel);
+ MessageGroupStore mgs = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
+ mgs.addMessageToGroup("foo", new GenericMessage("foo"));
+ mgs.completeGroup("foo");
+ mgs = spy(mgs);
+ new DirectFieldAccessor(handler).setPropertyValue("messageStore", mgs);
+ Method forceComplete = AbstractCorrelatingMessageHandler.class.getDeclaredMethod("forceComplete", MessageGroup.class);
+ forceComplete.setAccessible(true);
+ MessageGroup group = (MessageGroup) TestUtils.getPropertyValue(mgs, "groupIdToMessageGroup", Map.class).get("foo");
+ assertTrue(group.isComplete());
+ forceComplete.invoke(handler, group);
+ verify(mgs, never()).getMessageGroup("foo");
+ assertNull(outputChannel.receive(0));
+ }
+
+ /*
+ * INT-3216 - Verifies the complete early exit is taken after a refresh.
+ */
+ @Test
+ public void testDontReapIfAlreadyCompleteAfterRefetch() throws Exception {
+ MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
+ AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
+ handler.setReleaseStrategy(new ReleaseStrategy() {
+
+ @Override
+ public boolean canRelease(MessageGroup group) {
+ return true;
+ }
+
+ });
+ QueueChannel outputChannel = new QueueChannel();
+ handler.setOutputChannel(outputChannel);
+ MessageGroupStore mgs = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
+ mgs.addMessageToGroup("foo", new GenericMessage("foo"));
+ MessageGroup group = mgs.getMessageGroup("foo");
+ mgs.completeGroup("foo");
+ mgs = spy(mgs);
+ new DirectFieldAccessor(handler).setPropertyValue("messageStore", mgs);
+ Method forceComplete = AbstractCorrelatingMessageHandler.class.getDeclaredMethod("forceComplete", MessageGroup.class);
+ forceComplete.setAccessible(true);
+ MessageGroup groupInStore = (MessageGroup) TestUtils.getPropertyValue(mgs, "groupIdToMessageGroup", Map.class).get("foo");
+ assertTrue(groupInStore.isComplete());
+ assertFalse(group.isComplete());
+ new DirectFieldAccessor(group).setPropertyValue("lastModified", groupInStore.getLastModified());
+ forceComplete.invoke(handler, group);
+ verify(mgs).getMessageGroup("foo");
+ assertNull(outputChannel.receive(0));
+ }
+
+ /*
+ * INT-3216 - Verifies we don't complete if it's a completely new group (different timestamp).
+ */
+ @Test
+ public void testDontReapIfNewGroupFoundDuringRefetch() throws Exception {
+ MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
+ AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
+ handler.setReleaseStrategy(new ReleaseStrategy() {
+
+ @Override
+ public boolean canRelease(MessageGroup group) {
+ return true;
+ }
+
+ });
+ QueueChannel outputChannel = new QueueChannel();
+ handler.setOutputChannel(outputChannel);
+ MessageGroupStore mgs = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
+ mgs.addMessageToGroup("foo", new GenericMessage("foo"));
+ MessageGroup group = mgs.getMessageGroup("foo");
+ mgs = spy(mgs);
+ new DirectFieldAccessor(handler).setPropertyValue("messageStore", mgs);
+ Method forceComplete = AbstractCorrelatingMessageHandler.class.getDeclaredMethod("forceComplete", MessageGroup.class);
+ forceComplete.setAccessible(true);
+ MessageGroup groupInStore = (MessageGroup) TestUtils.getPropertyValue(mgs, "groupIdToMessageGroup", Map.class).get("foo");
+ assertFalse(groupInStore.isComplete());
+ assertFalse(group.isComplete());
+ DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(group);
+ directFieldAccessor.setPropertyValue("lastModified", groupInStore.getLastModified());
+ directFieldAccessor.setPropertyValue("timestamp", groupInStore.getTimestamp() - 1);
+ forceComplete.invoke(handler, group);
+ verify(mgs).getMessageGroup("foo");
+ assertNull(outputChannel.receive(0));
+ }
+
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java
index c1a94c0ace..53b0235b01 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/MethodInvokingMessageGroupProcessorTests.java
@@ -308,10 +308,9 @@ public class MethodInvokingMessageGroupProcessorTests {
assertTrue(((Message>)result).getPayload() instanceof Iterator>);
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void testTwoMethodsWithSameParameterTypesAmbiguous() {
- @SuppressWarnings("unused")
class AnnotatedParametersAggregator {
public Integer and(List flags) {
int result = 0;
@@ -327,7 +326,12 @@ public class MethodInvokingMessageGroupProcessorTests {
}
}
- new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator());
+ MessageGroupProcessor processor = new MethodInvokingMessageGroupProcessor(new AnnotatedParametersAggregator());
+ when(messageGroupMock.getMessages()).thenReturn(messagesUpForProcessing);
+ Object result = processor.processMessageGroup(messageGroupMock);
+ Object payload = ((Message>) result).getPayload();
+ assertTrue(payload instanceof Integer);
+ assertEquals(7, payload);
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/registry/HeaderChannelRegistryTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/channel/registry/HeaderChannelRegistryTests-context.xml
new file mode 100644
index 0000000000..5e073d435e
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/registry/HeaderChannelRegistryTests-context.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/registry/HeaderChannelRegistryTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/registry/HeaderChannelRegistryTests.java
new file mode 100644
index 0000000000..483ab37699
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/channel/registry/HeaderChannelRegistryTests.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.channel.registry;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.channel.MessagePublishingErrorHandler;
+import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.core.GenericMessagingTemplate;
+import org.springframework.messaging.support.ErrorMessage;
+import org.springframework.messaging.support.GenericMessage;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class HeaderChannelRegistryTests {
+
+ @Autowired
+ MessageChannel input;
+
+ @Autowired
+ MessageChannel inputPolled;
+
+ @Autowired
+ QueueChannel alreadyAString;
+
+ @Autowired
+ TaskScheduler taskScheduler;
+
+ @Autowired
+ Gateway gatewayNoReplyChannel;
+
+ @Autowired
+ Gateway gatewayExplicitReplyChannel;
+
+ @Test
+ public void testReplace() {
+ GenericMessagingTemplate template = new GenericMessagingTemplate();
+ template.setDefaultDestination(this.input);
+ Message> reply = template.sendAndReceive(new GenericMessage("foo"));
+ assertNotNull(reply);
+ assertEquals("echo:foo", reply.getPayload());
+ }
+
+ @Test
+ public void testReplaceGatewayWithNoReplyChannel() {
+ String reply = this.gatewayNoReplyChannel.exchange("foo");
+ assertNotNull(reply);
+ assertEquals("echo:foo", reply);
+ }
+
+ @Test
+ public void testReplaceGatewayWithExplicitReplyChannel() {
+ String reply = this.gatewayExplicitReplyChannel.exchange("foo");
+ assertNotNull(reply);
+ assertEquals("echo:foo", reply);
+ }
+
+ /**
+ * MessagingTemplate sets the errorChannel to the replyChannel so it gets any async
+ * exceptions via the default {@link MessagePublishingErrorHandler}.
+ */
+ @Test
+ public void testReplaceError() {
+ GenericMessagingTemplate template = new GenericMessagingTemplate();
+ template.setDefaultDestination(this.inputPolled);
+ Message> reply = template.sendAndReceive(new GenericMessage("bar"));
+ assertNotNull(reply);
+ assertTrue(reply instanceof ErrorMessage);
+ }
+
+ @Test
+ public void testAlreadyAString() {
+ Message requestMessage = MessageBuilder.withPayload("foo")
+ .setReplyChannelName("alreadyAString")
+ .setErrorChannelName("alreadyAnotherString")
+ .build();
+ this.input.send(requestMessage);
+ Message> reply = alreadyAString.receive(0);
+ assertNotNull(reply);
+ assertEquals("echo:foo", reply.getPayload());
+ }
+
+ @Test
+ public void testNull() {
+ Message requestMessage = MessageBuilder.withPayload("foo")
+ .build();
+ try {
+ this.input.send(requestMessage);
+ fail("expected exception");
+ }
+ catch (Exception e) {
+ assertThat(e.getMessage(), Matchers.containsString("no output-channel or replyChannel"));
+ }
+ }
+
+ @Test
+ public void testExpire() throws Exception {
+ DefaultHeaderChannelRegistry registry = new DefaultHeaderChannelRegistry(50);
+ registry.setTaskScheduler(this.taskScheduler);
+ registry.start();
+ Thread.sleep(200);
+ String id = (String) registry.channelToChannelName(new DirectChannel());
+ Thread.sleep(300);
+ assertNull(registry.channelNameToChannel(id));
+ registry.stop();
+ }
+
+ public static class Foo extends AbstractReplyProducingMessageHandler {
+
+ @Override
+ protected Object handleRequestMessage(Message> requestMessage) {
+ assertThat(requestMessage.getHeaders().getReplyChannel(),
+ Matchers.anyOf(instanceOf(String.class), Matchers.nullValue()));
+ assertThat(requestMessage.getHeaders().getErrorChannel(),
+ Matchers.anyOf(instanceOf(String.class), Matchers.nullValue()));
+ if (requestMessage.getPayload().equals("bar")) {
+ throw new RuntimeException("intentional");
+ }
+ return "echo:" + requestMessage.getPayload();
+ }
+
+ }
+
+ public interface Gateway {
+
+ String exchange(String foo);
+
+ }
+
+}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java
index 8b1b469baa..b106e54315 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java
@@ -19,18 +19,19 @@ package org.springframework.integration.config;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
@@ -121,14 +122,14 @@ public class AggregatorParserTests {
Object consumer = new DirectFieldAccessor(endpoint).getPropertyValue("handler");
assertThat(consumer, is(instanceOf(AggregatingMessageHandler.class)));
DirectFieldAccessor accessor = new DirectFieldAccessor(consumer);
- Map, ?> map = (Map, ?>) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor
+ Object handlerMethods = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor
.getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate"))
.getPropertyValue("handlerMethods");
- assertEquals("The MethodInvokingAggregator is not injected with the appropriate aggregation method", 1, map
- .size());
- assertEquals("The release strategy is not injected with the appropriate method", 1, map.size());
- assertTrue("Handler methods do not contain correct method: " + map, map.toString().contains(
- "createSingleMessageFromGroup"));
+ assertNull(handlerMethods);
+ Object handlerMethod = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(accessor
+ .getPropertyValue("outputProcessor")).getPropertyValue("processor")).getPropertyValue("delegate"))
+ .getPropertyValue("handlerMethod");
+ assertTrue(handlerMethod.toString().contains("createSingleMessageFromGroup"));
assertEquals("The AggregatorEndpoint is not injected with the appropriate ReleaseStrategy instance",
releaseStrategy, accessor.getPropertyValue("releaseStrategy"));
assertEquals("The AggregatorEndpoint is not injected with the appropriate CorrelationStrategy instance",
@@ -178,10 +179,10 @@ public class AggregatorParserTests {
Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy);
DirectFieldAccessor releaseStrategyAccessor = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategy)
.getPropertyValue("adapter")).getPropertyValue("delegate"));
- Map, ?> map = (Map, ?>) releaseStrategyAccessor.getPropertyValue("handlerMethods");
- assertEquals("The release strategy is not injected with the appropriate method", 1, map.size());
- assertTrue("Handler methods do not contain correct method: " + map, map.toString()
- .contains("checkCompleteness"));
+ Object handlerMethods = releaseStrategyAccessor.getPropertyValue("handlerMethods");
+ assertNull(handlerMethods);
+ Object handlerMethod = releaseStrategyAccessor.getPropertyValue("handlerMethod");
+ assertTrue(handlerMethod.toString().contains("checkCompleteness"));
input.send(createMessage(1l, "correllationId", 4, 0, null));
input.send(createMessage(2l, "correllationId", 4, 1, null));
input.send(createMessage(3l, "correllationId", 4, 2, null));
@@ -203,10 +204,10 @@ public class AggregatorParserTests {
Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy);
DirectFieldAccessor releaseStrategyAccessor = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategy)
.getPropertyValue("adapter")).getPropertyValue("delegate"));
- Map, ?> map = (Map, ?>) releaseStrategyAccessor.getPropertyValue("handlerMethods");
- assertEquals("The release strategy is not injected with the appropriate method", 1, map.size());
- assertTrue("Handler methods do not contain correct method: " + map, map.toString()
- .contains("checkCompleteness"));
+ Object handlerMethods = releaseStrategyAccessor.getPropertyValue("handlerMethods");
+ assertNull(handlerMethods);
+ Object handlerMethod = releaseStrategyAccessor.getPropertyValue("handlerMethod");
+ assertTrue(handlerMethod.toString().contains("checkCompleteness"));
input.send(createMessage(1l, "correllationId", 4, 0, null));
input.send(createMessage(2l, "correllationId", 4, 1, null));
input.send(createMessage(3l, "correllationId", 4, 2, null));
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests-context.xml
index c7da41a0c6..c079fdca07 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests-context.xml
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests-context.xml
@@ -40,7 +40,7 @@
-
+
@@ -49,7 +49,7 @@
-
+
-
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests.java
index 5ef3a7db3d..f1c3269ea4 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests.java
@@ -172,7 +172,7 @@ public class ChainParserTests {
public void chainWithAcceptingFilter() {
Message> message = MessageBuilder.withPayload("test").build();
this.filterInput.send(message);
- Message> reply = this.output.receive(0);
+ Message> reply = this.output.receive(1000);
assertNotNull(reply);
assertEquals("foo", reply.getPayload());
}
@@ -189,7 +189,7 @@ public class ChainParserTests {
public void chainWithHeaderEnricher() {
Message> message = MessageBuilder.withPayload(123).build();
this.headerEnricherInput.send(message);
- Message> reply = this.replyOutput.receive(0);
+ Message> reply = this.replyOutput.receive(1000);
assertNotNull(reply);
assertEquals("foo", reply.getPayload());
assertEquals("ABC", new EiMessageHeaderAccessor(reply).getCorrelationId());
@@ -240,8 +240,8 @@ public class ChainParserTests {
Message> message2 = MessageBuilder.withPayload(123).build();
this.payloadTypeRouterInput.send(message1);
this.payloadTypeRouterInput.send(message2);
- Message> reply1 = this.strings.receive(0);
- Message> reply2 = this.numbers.receive(0);
+ Message> reply1 = this.strings.receive(1000);
+ Message> reply2 = this.numbers.receive(1000);
assertNotNull(reply1);
assertNotNull(reply2);
assertEquals("test", reply1.getPayload());
@@ -254,8 +254,8 @@ public class ChainParserTests {
Message> message2 = MessageBuilder.withPayload(123).setHeader("routingHeader", "numbers").build();
this.headerValueRouterInput.send(message1);
this.headerValueRouterInput.send(message2);
- Message> reply1 = this.strings.receive(0);
- Message> reply2 = this.numbers.receive(0);
+ Message> reply1 = this.strings.receive(1000);
+ Message> reply2 = this.numbers.receive(1000);
assertNotNull(reply1);
assertNotNull(reply2);
assertEquals("test", reply1.getPayload());
@@ -302,6 +302,7 @@ public class ChainParserTests {
final AtomicReference log = new AtomicReference();
when(logger.isWarnEnabled()).thenReturn(true);
doAnswer(new Answer() {
+ @Override
public Object answer(InvocationOnMock invocation) throws Throwable {
log.set((String) invocation.getArguments()[0]);
return null;
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java
index 490ed89f63..5baa25f5c0 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,16 +17,17 @@
package org.springframework.integration.config.annotation;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
import java.lang.reflect.Method;
-import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
+
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -45,6 +46,7 @@ import org.springframework.messaging.core.DestinationResolver;
/**
* @author Marius Bogoevici
* @author Mark Fisher
+ * @author Artem Bilan
*/
public class AggregatorAnnotationTests {
@@ -86,13 +88,12 @@ public class AggregatorAnnotationTests {
Object releaseStrategy = getPropertyValue(aggregator, "releaseStrategy");
Assert.assertTrue(releaseStrategy instanceof MethodInvokingReleaseStrategy);
MethodInvokingReleaseStrategy releaseStrategyAdapter = (MethodInvokingReleaseStrategy) releaseStrategy;
- Map, ?> map = (Map, ?>) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter)
+ Object handlerMethods = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter)
.getPropertyValue("adapter")).getPropertyValue("delegate")).getPropertyValue("handlerMethods");
- assertEquals("The MethodInvokingAggregator is not injected with the appropriate aggregation method", 1, map
- .size());
- assertEquals("The release strategy is not injected with the appropriate method", 1, map.size());
- assertTrue("Handler methods do not contain correct method: " + map, map.toString()
- .contains("completionChecker"));
+ assertNull(handlerMethods);
+ Object handlerMethod = new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(releaseStrategyAdapter)
+ .getPropertyValue("adapter")).getPropertyValue("delegate")).getPropertyValue("handlerMethod");
+ assertTrue(handlerMethod.toString().contains("completionChecker"));
}
@Test
@@ -108,9 +109,10 @@ public class AggregatorAnnotationTests {
.getPropertyValue("processor")).getPropertyValue("delegate"));
Object targetObject = processorAccessor.getPropertyValue("targetObject");
assertSame(context.getBean(endpointName), targetObject);
- Map, ?> handlerMethods = (Map, ?>) processorAccessor.getPropertyValue("handlerMethods");
- assertEquals(1, handlerMethods.size());
- DirectFieldAccessor handlerMethodAccessor = new DirectFieldAccessor(handlerMethods.values().iterator().next());
+ assertNull(processorAccessor.getPropertyValue("handlerMethods"));
+ Object handlerMethod = processorAccessor.getPropertyValue("handlerMethod");
+ assertNotNull(handlerMethod);
+ DirectFieldAccessor handlerMethodAccessor = new DirectFieldAccessor(handlerMethod);
Method completionCheckerMethod = (Method) handlerMethodAccessor.getPropertyValue("method");
assertEquals("correlate", completionCheckerMethod.getName());
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java
index 8d61adb41b..37c24d202c 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java
@@ -16,8 +16,8 @@
package org.springframework.integration.config.xml;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.Date;
@@ -28,12 +28,15 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.integration.channel.DefaultHeaderChannelRegistry;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
+import org.springframework.messaging.core.GenericMessagingTemplate;
import org.springframework.messaging.support.GenericMessage;
-import org.springframework.integration.support.MessageBuilder;
-import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -52,6 +55,9 @@ public class ControlBusTests {
@Autowired
private PollableChannel output;
+ @Autowired
+ private DefaultHeaderChannelRegistry registry;
+
@Test
public void testDefaultEvaluationContext() {
Message> message = MessageBuilder.withPayload("@service.convert('aardvark')+headers.foo").setHeader("foo", "bar").build();
@@ -71,6 +77,27 @@ public class ControlBusTests {
assertNotNull(outputChannel.receive(1000));
}
+ @Test
+ public void testControlHeaderChannelReaper() throws InterruptedException {
+ GenericMessagingTemplate messagingTemplate = new GenericMessagingTemplate();
+ messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.size()");
+ Message> result = this.output.receive(0);
+ assertNotNull(result);
+ assertEquals(0, result.getPayload());
+ this.registry.setReaperDelay(10);
+ this.registry.channelToChannelName(new DirectChannel());
+ messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.size()");
+ result = this.output.receive(0);
+ assertNotNull(result);
+ assertEquals(1, result.getPayload());
+ Thread.sleep(100);
+ messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.runReaper()");
+ messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.size()");
+ result = this.output.receive(0);
+ assertNotNull(result);
+ assertEquals(0, result.getPayload());
+ this.registry.setReaperDelay(60000);
+ }
public static class Service {
@@ -78,12 +105,14 @@ public class ControlBusTests {
public String convert(String input) {
return "cat";
}
+
}
public static class AdapterService {
public Message receive() {
return new GenericMessage(new Date().toString());
}
+
}
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml
index b0c558e552..f62be84be9 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml
@@ -13,11 +13,17 @@
+
+
+
+
+
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java
index 91753f66f6..782bddbc08 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java
@@ -17,6 +17,7 @@
package org.springframework.integration.config.xml;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
@@ -79,6 +80,7 @@ public class EnricherParserTests {
assertEquals(context.getBean("output"), accessor.getPropertyValue("outputChannel"));
assertEquals(true, accessor.getPropertyValue("shouldClonePayload"));
assertNull(accessor.getPropertyValue("requestPayloadExpression"));
+ assertNotNull(TestUtils.getPropertyValue(enricher, "gateway.beanFactory"));
Map propertyExpressions = (Map) accessor.getPropertyValue("propertyExpressions");
for (Map.Entry e : propertyExpressions.entrySet()) {
@@ -125,12 +127,15 @@ public class EnricherParserTests {
@Test
public void integrationTest() {
SubscribableChannel requests = context.getBean("requests", SubscribableChannel.class);
- requests.subscribe(new AbstractReplyProducingMessageHandler() {
+ class Foo extends AbstractReplyProducingMessageHandler {
@Override
protected Object handleRequestMessage(Message> requestMessage) {
return new Source("foo");
}
- });
+ };
+ Foo foo = new Foo();
+ foo.setOutputChannel(context.getBean("replies", MessageChannel.class));
+ requests.subscribe(foo);
Target original = new Target();
Message> request = MessageBuilder.withPayload(original)
.setHeader("sourceName", "test")
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-after-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-after-context.xml
index d96e063f15..b177646005 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-after-context.xml
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-after-context.xml
@@ -7,7 +7,7 @@
-
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-before-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-before-context.xml
index 5ce379cb63..ece1f92d91 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-before-context.xml
+++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/TestEnableChannelAutoCreation-before-context.xml
@@ -5,7 +5,7 @@
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.xsd">
-
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/context/IntegrationContextTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/context/IntegrationContextTests-context.xml
new file mode 100644
index 0000000000..4bbd23523b
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/context/IntegrationContextTests-context.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/context/IntegrationContextTests.java b/spring-integration-core/src/test/java/org/springframework/integration/context/IntegrationContextTests.java
new file mode 100644
index 0000000000..64d9ee572e
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/context/IntegrationContextTests.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.integration.context;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.Properties;
+
+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.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Artem Bilan
+ * @since 3.0
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class IntegrationContextTests {
+
+ @Autowired
+ @Qualifier(IntegrationContextUtils.INTEGRATION_PROPERTIES_BEAN_NAME)
+ private Properties integrationProperties;
+
+ @Autowired
+ @Qualifier("fooService")
+ private IntegrationObjectSupport serviceActivator;
+
+ @Test
+ public void testIntegrationContextComponents() {
+ assertEquals("error", this.integrationProperties.get(IntegrationProperties.LATE_REPLY_LOGGING_LEVEL));
+ assertSame(this.integrationProperties, this.serviceActivator.getIntegrationProperties());
+ }
+
+}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ProducerAndConsumerAutoStartupTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ProducerAndConsumerAutoStartupTests-context.xml
index 1feb6b2cb0..7f873d3914 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ProducerAndConsumerAutoStartupTests-context.xml
+++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ProducerAndConsumerAutoStartupTests-context.xml
@@ -13,7 +13,7 @@
-
+
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ServiceActivatorMethodResolutionTests.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ServiceActivatorMethodResolutionTests.java
index 8247371486..b058d39f76 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ServiceActivatorMethodResolutionTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/ServiceActivatorMethodResolutionTests.java
@@ -17,13 +17,19 @@
package org.springframework.integration.endpoint;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.Date;
import org.junit.Test;
-import org.springframework.messaging.Message;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.gateway.RequestReplyExchanger;
import org.springframework.integration.handler.ServiceActivatingHandler;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
/**
@@ -66,6 +72,166 @@ public class ServiceActivatorMethodResolutionTests {
}
+ @Test
+ public void testRequestReplyExchanger() {
+ RequestReplyExchanger testBean = new RequestReplyExchanger() {
+
+ @Override
+ public Message> exchange(Message> request) {
+ return request;
+ }
+ };
+
+ final Message> test = new GenericMessage("foo");
+
+ ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean) {
+
+ @Override
+ protected Object handleRequestMessage(Message> message) {
+ Object o = super.handleRequestMessage(message);
+ assertSame(test, o);
+ return null;
+ }
+ };
+
+ serviceActivator.handleMessage(test);
+ }
+
+ @Test
+ /*
+ * A handler and message handler fallback (RRE); don't force RRE
+ */
+ public void testRequestReplyExchangerSeveralMethods() {
+ RequestReplyExchanger testBean = new RequestReplyExchanger() {
+
+ @Override
+ public Message> exchange(Message> request) {
+ return request;
+ }
+
+ @SuppressWarnings("unused")
+ public String foo(String request) {
+ return request.toUpperCase();
+ }
+
+ };
+ ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
+ PollableChannel outputChannel = new QueueChannel();
+ serviceActivator.setOutputChannel(outputChannel);
+
+ Message> test = new GenericMessage(new Date());
+ serviceActivator.handleMessage(test);
+ assertEquals(test, outputChannel.receive(10));
+
+ test = new GenericMessage("foo");
+ serviceActivator.handleMessage(test);
+ assertEquals("FOO", outputChannel.receive(10).getPayload());
+ }
+
+ @Test
+ /*
+ * No handler fallback methods; don't force RRE
+ */
+ public void testRequestReplyExchangerWithGenericMessageMethod() {
+ RequestReplyExchanger testBean = new RequestReplyExchanger() {
+
+ @Override
+ public Message> exchange(Message> request) {
+ return request;
+ }
+
+ @SuppressWarnings("unused")
+ public String foo(Message request) {
+ return request.getPayload().toUpperCase();
+ }
+
+ };
+ ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
+ PollableChannel outputChannel = new QueueChannel();
+ serviceActivator.setOutputChannel(outputChannel);
+
+ Message> test = new GenericMessage(new Date());
+ serviceActivator.handleMessage(test);
+ assertEquals(test, outputChannel.receive(10));
+
+ test = new GenericMessage("foo");
+ serviceActivator.handleMessage(test);
+ assertEquals("FOO", outputChannel.receive(10).getPayload());
+ }
+
+ @Test
+ /*
+ * No handler fallback methods; ambiguous message handler fallbacks; force RRE
+ */
+ public void testRequestReplyExchangerWithAmbiguousGenericMessageMethod() {
+ RequestReplyExchanger testBean = new RequestReplyExchanger() {
+
+ @Override
+ public Message> exchange(Message> request) {
+ return request;
+ }
+
+ @SuppressWarnings("unused")
+ public String foo(Message request) {
+ return request.getPayload().toUpperCase();
+ }
+
+ @SuppressWarnings("unused")
+ public String bar(Message request) {
+ return request.getPayload().toUpperCase();
+ }
+
+ };
+ ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
+ PollableChannel outputChannel = new QueueChannel();
+ serviceActivator.setOutputChannel(outputChannel);
+
+ Message> test = new GenericMessage(new Date());
+ serviceActivator.handleMessage(test);
+ assertEquals(test, outputChannel.receive(10));
+
+ test = new GenericMessage("foo");
+ serviceActivator.handleMessage(test);
+ assertNotEquals("FOO", outputChannel.receive(10).getPayload());
+ }
+
+ @Test
+ /*
+ * One message handler fallback method (RRE); ambiguous handler fallbacks; force RRE
+ */
+ public void testRequestReplyExchangerWithAmbiguousMethod() {
+ RequestReplyExchanger testBean = new RequestReplyExchanger() {
+
+ @Override
+ public Message> exchange(Message> request) {
+ return request;
+ }
+
+ @SuppressWarnings("unused")
+ public String foo(String request) {
+ return request.toUpperCase();
+ }
+
+ @SuppressWarnings("unused")
+ public String bar(String request) {
+ return request.toUpperCase();
+ }
+
+ };
+ ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
+ PollableChannel outputChannel = new QueueChannel();
+ serviceActivator.setOutputChannel(outputChannel);
+
+ Message> test = new GenericMessage(new Date());
+ serviceActivator.handleMessage(test);
+ assertEquals(test, outputChannel.receive(10));
+
+ test = new GenericMessage("foo");
+ serviceActivator.handleMessage(test);
+ assertNotEquals("FOO", outputChannel.receive(10).getPayload());
+ }
+
+
@SuppressWarnings("unused")
private static class SingleAnnotationTestBean {
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java
index bd4961ec47..4c17cfb11a 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java
@@ -20,18 +20,23 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
import java.lang.reflect.Method;
+import java.util.Date;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.Description;
+import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.messaging.Message;
import org.springframework.integration.MessageHandlingException;
@@ -355,6 +360,135 @@ public class MethodInvokingMessageProcessorTests {
assertSame(RequestReplyExchanger.class, result);
}
+ @Test
+ public void testInt3199GenericTypeResolvingAndObjectMethod() throws Exception {
+
+ class Foo {
+
+ public String handleMessage(Message message) {
+ return "" + (message.getPayload().intValue() * 2);
+ }
+
+ public String objectMethod(Integer foo) {
+ return foo.toString();
+ }
+
+ public String voidMethod() {
+ return "foo";
+ }
+
+ }
+
+ MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(new Foo(), (String) null, false);
+ assertEquals("4", helper.process(new GenericMessage(2L)));
+ assertEquals("1", helper.process(new GenericMessage(1)));
+ assertEquals("foo", helper.process(new GenericMessage(new Date())));
+ }
+
+ @Test
+ public void testInt3199GettersAmbiguity() throws Exception {
+
+ class Foo {
+
+ public String getFoo() {
+ return "foo";
+ }
+
+ public String getBar() {
+ return "foo";
+ }
+ }
+
+ try {
+ new MessagingMethodInvokerHelper(new Foo(), (String) null, false);
+ fail("IllegalArgumentException expected");
+ }
+ catch (Exception e) {
+ assertThat(e, Matchers.instanceOf(IllegalArgumentException.class));
+ assertEquals("Found more than one method match for empty parameter for 'payload'", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testInt3199MessageMethods() throws Exception {
+
+ class Foo {
+
+ public String m1(Message message) {
+ return message.getPayload();
+ }
+
+ public Integer m2(Message message) {
+ return message.getPayload();
+ }
+
+ public Object m3(Message> message) {
+ return message.getPayload();
+ }
+
+ }
+
+ Foo targetObject = new Foo();
+
+ MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
+ assertEquals("foo", helper.process(new GenericMessage("foo")));
+ assertEquals(1, helper.process(new GenericMessage(1)));
+ assertEquals(targetObject, helper.process(new GenericMessage(targetObject)));
+ }
+
+ @Test
+ public void testInt3199TypedMethods() throws Exception {
+
+ class Foo {
+
+ public String m1(String payload) {
+ return payload;
+ }
+
+ public Integer m2(Integer payload) {
+ return payload;
+ }
+
+ public Object m3(Object payload) {
+ return payload;
+ }
+
+ }
+
+ Foo targetObject = new Foo();
+
+ MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
+ assertEquals("foo", helper.process(new GenericMessage("foo")));
+ assertEquals(1, helper.process(new GenericMessage(1)));
+ assertEquals(targetObject, helper.process(new GenericMessage(targetObject)));
+ }
+
+ @Test
+ public void testInt3199PrecedenceOfCandidates() throws Exception {
+
+ class Foo {
+
+ public Object m1(Message message) {
+ fail("This method must not be invoked");
+ return message;
+ }
+
+ public Object m2(String payload) {
+ return payload;
+ }
+
+ public Object m3() {
+ return "FOO";
+ }
+ }
+
+ Foo targetObject = new Foo();
+
+ MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
+ assertEquals("foo", helper.process(new GenericMessage("foo")));
+ assertEquals("FOO", helper.process(new GenericMessage(targetObject)));
+ }
+
private static class ExceptionCauseMatcher extends TypeSafeMatcher {
private Throwable cause;
@@ -528,5 +662,7 @@ public class MethodInvokingMessageProcessorTests {
this.lastArg = s;
return s;
}
+
}
+
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPathTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPathTests.java
index 9e10b40125..85bcddc94e 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPathTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPathTests.java
@@ -53,6 +53,7 @@ import com.jayway.jsonpath.Filter;
/**
* @author Artem Bilan
+ * @author Gary Russell
* @since 3.0
*/
@ContextConfiguration(classes = JsonPathTests.JsonPathTestsContextConfiguration.class, loader = AnnotationConfigContextLoader.class)
@@ -69,7 +70,9 @@ public class JsonPathTests {
public static void setUp() throws IOException {
ClassPathResource jsonResource = new ClassPathResource("JsonPathTests.json", JsonPathTests.class);
JSON_FILE = jsonResource.getFile();
- JSON = new Scanner(JSON_FILE).useDelimiter("\\Z").next();
+ Scanner scanner = new Scanner(JSON_FILE);
+ JSON = scanner.useDelimiter("\\Z").next();
+ scanner.close();
testMessage = new GenericMessage(JSON);
}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java b/spring-integration-core/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java
index f358481518..792ecb43c9 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/message/PayloadAndHeaderMappingTests.java
@@ -749,14 +749,14 @@ public class PayloadAndHeaderMappingTests {
this.lastHeaders.put("foo", header);
this.lastPayload = payload;
}
-
+
public void payloadMapAndHeaderStrings(Map payload, @Header("foo") String header1, @Header("bar") String header2) {
this.lastHeaders = new HashMap();
this.lastHeaders.put("foo", header1);
this.lastHeaders.put("bar", header2);
- this.lastPayload = payload;
+ this.lastPayload = payload;
}
-
+
public void payloadMapAndHeaderMap(Map payload, @Headers Map headers) {
this.lastHeaders = headers;
this.lastPayload = payload;
@@ -771,7 +771,7 @@ public class PayloadAndHeaderMappingTests {
this.lastHeaders = headers;
this.lastPayload = payload;
}
-
+
public void headerPropertiesPayloadMapAndStringHeader(@Headers Properties headers, Map payload, @Header("foo") String header) {
this.lastHeaders = headers;
this.lastHeaders.put("foo2", header);
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests.java
index 560dd358b2..a0988e35e1 100644
--- a/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests.java
+++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/SpelTransformerIntegrationTests.java
@@ -171,7 +171,7 @@ public class SpelTransformerIntegrationTests {
@Override
public Class>[] getSpecificTargetClasses() {
- return new Class[] {Foo.class};
+ return new Class>[] {Foo.class};
}
@Override
diff --git a/spring-integration-core/src/test/resources/META-INF/spring.integration.properties b/spring-integration-core/src/test/resources/META-INF/spring.integration.properties
new file mode 100644
index 0000000000..38ffd39c66
--- /dev/null
+++ b/spring-integration-core/src/test/resources/META-INF/spring.integration.properties
@@ -0,0 +1,2 @@
+#channels.autoCreate=false
+messagingTemplate.lateReply.logging.level=error
diff --git a/spring-integration-event/src/main/java/org/springframework/integration/event/inbound/ApplicationEventListeningMessageProducer.java b/spring-integration-event/src/main/java/org/springframework/integration/event/inbound/ApplicationEventListeningMessageProducer.java
index 48a2076676..77dbac0d28 100644
--- a/spring-integration-event/src/main/java/org/springframework/integration/event/inbound/ApplicationEventListeningMessageProducer.java
+++ b/spring-integration-event/src/main/java/org/springframework/integration/event/inbound/ApplicationEventListeningMessageProducer.java
@@ -21,8 +21,9 @@ import java.util.Set;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
-import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ApplicationEventMulticaster;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
@@ -40,6 +41,8 @@ import org.springframework.util.CollectionUtils;
*
* @author Mark Fisher
* @author Artem Bilan
+ * @author Gary Russell
+ *
* @see ApplicationEventMulticaster
* @see ExpressionMessageProducerSupport
*/
@@ -51,6 +54,10 @@ public class ApplicationEventListeningMessageProducer extends ExpressionMessageP
private volatile boolean active;
+ private volatile long stoppedAt;
+
+ private volatile boolean phaseSet;
+
/**
* Set the list of event types (classes that extend ApplicationEvent) that
* this adapter should send to the message channel. By default, all event
@@ -73,6 +80,12 @@ public class ApplicationEventListeningMessageProducer extends ExpressionMessageP
}
}
+ @Override
+ public void setPhase(int phase) {
+ super.setPhase(phase);
+ this.phaseSet = true;
+ }
+
@Override
public String getComponentType() {
return "event:inbound-channel-adapter";
@@ -85,10 +98,15 @@ public class ApplicationEventListeningMessageProducer extends ExpressionMessageP
.getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
Assert.notNull(this.applicationEventMulticaster,
"To use ApplicationListeners the 'applicationEventMulticaster' bean must be supplied within ApplicationContext.");
+ if (!this.phaseSet) {
+ super.setPhase(Integer.MIN_VALUE + 1000);
+ }
}
+ @Override
public void onApplicationEvent(ApplicationEvent event) {
- if (this.active || event instanceof ApplicationContextEvent) {
+ if (this.active || ((event instanceof ContextStoppedEvent || event instanceof ContextClosedEvent)
+ && this.stoppedRecently())) {
if (event.getSource() instanceof Message>) {
this.sendMessage((Message>) event.getSource());
}
@@ -99,6 +117,11 @@ public class ApplicationEventListeningMessageProducer extends ExpressionMessageP
}
}
+ private boolean stoppedRecently() {
+ return this.stoppedAt > System.currentTimeMillis() - 5000;
+ }
+
+ @Override
public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
if (this.eventTypes == null) {
return true;
@@ -111,10 +134,12 @@ public class ApplicationEventListeningMessageProducer extends ExpressionMessageP
return false;
}
+ @Override
public boolean supportsSourceType(Class> sourceType) {
return true;
}
+ @Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@@ -126,6 +151,7 @@ public class ApplicationEventListeningMessageProducer extends ExpressionMessageP
@Override
protected void doStop() {
+ this.stoppedAt = System.currentTimeMillis();
this.active = false;
}
diff --git a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests.java b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests.java
index dc72323a54..7532d31860 100644
--- a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests.java
+++ b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests.java
@@ -20,9 +20,9 @@ import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import 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.context.ApplicationEvent;
@@ -71,6 +71,7 @@ public class EventOutboundChannelAdapterParserTests {
@Test
public void validateUsage() {
ApplicationListener> listener = new ApplicationListener() {
+ @Override
public void onApplicationEvent(ApplicationEvent event) {
Object source = event.getSource();
if (source instanceof Message){
@@ -91,6 +92,7 @@ public class EventOutboundChannelAdapterParserTests {
public void withAdvice() {
receivedEvent = false;
ApplicationListener> listener = new ApplicationListener() {
+ @Override
public void onApplicationEvent(ApplicationEvent event) {
Object source = event.getSource();
if (source instanceof Message){
@@ -112,6 +114,7 @@ public class EventOutboundChannelAdapterParserTests {
public void testInsideChain() {
receivedEvent = false;
ApplicationListener> listener = new ApplicationListener() {
+ @Override
public void onApplicationEvent(ApplicationEvent event) {
Object source = event.getSource();
if (source instanceof Message){
@@ -128,12 +131,13 @@ public class EventOutboundChannelAdapterParserTests {
Assert.assertTrue(receivedEvent);
}
- @Test(timeout=2000)
+ @Test(timeout=10000)
public void validateUsageWithPollableChannel() throws Exception {
receivedEvent = false;
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("EventOutboundChannelAdapterParserTestsWithPollable-context.xml", EventOutboundChannelAdapterParserTests.class);
final CyclicBarrier barier = new CyclicBarrier(2);
ApplicationListener> listener = new ApplicationListener() {
+ @Override
public void onApplicationEvent(ApplicationEvent event) {
Object source = event.getSource();
if (source instanceof Message){
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java b/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java
index 487117ef2a..d839ce0ead 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,11 @@ package org.springframework.integration.file;
import java.io.File;
-import org.springframework.messaging.Message;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.util.AbstractExpressionEvaluator;
+import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -34,13 +37,17 @@ import org.springframework.util.StringUtils;
* associated with the header if no expression has been provided), it checks if
* the Message payload is a File instance, and if so, it uses the same name.
* Finally, it falls back to the Message ID and adds the suffix '.msg'.
- *
+ *
* @author Mark Fisher
+ * @author Gary Russell
*/
public class DefaultFileNameGenerator extends AbstractExpressionEvaluator implements FileNameGenerator {
- private volatile String expression = "headers['" + FileHeaders.FILENAME + "']";
+ private static final String DEFAULT_EXPRESSION = "headers['" + FileHeaders.FILENAME + "']";
+ private final static ExpressionParser parser = new SpelExpressionParser();
+
+ private volatile Expression expression = parser.parseExpression(DEFAULT_EXPRESSION);
/**
* Specify an expression to be evaluated against the Message
@@ -48,7 +55,7 @@ public class DefaultFileNameGenerator extends AbstractExpressionEvaluator implem
*/
public void setExpression(String expression) {
Assert.hasText(expression, "expression must not be empty");
- this.expression = expression;
+ this.expression = parser.parseExpression(expression);
}
/**
@@ -57,9 +64,10 @@ public class DefaultFileNameGenerator extends AbstractExpressionEvaluator implem
*/
public void setHeaderName(String headerName) {
Assert.notNull(headerName, "'headerName' must not be null");
- this.expression = "headers['" + headerName + "']";
+ this.expression = parser.parseExpression("headers['" + headerName + "']");
}
+ @Override
public String generateFileName(Message> message) {
Object filenameProperty = this.evaluateExpression(this.expression, message);
if (filenameProperty instanceof String && StringUtils.hasText((String) filenameProperty)) {
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java
index 8fc19e0bc2..2e90df8dd1 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java
@@ -23,6 +23,8 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.file.filters.RegexPatternFileListFilter;
+import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import org.springframework.util.StringUtils;
/**
@@ -42,18 +44,20 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
+
+ BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, false);
+
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(getGatewayClassName());
- builder.addConstructorArgReference(element.getAttribute("session-factory"));
+ builder.addConstructorArgValue(templateDefinition);
builder.addConstructorArgValue(element.getAttribute("command"));
builder.addConstructorArgValue(element.getAttribute(EXPRESSION_ATTRIBUTE));
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "command-options", "options");
- IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "temporary-file-suffix");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
- this.configureFilter(builder, element, parserContext);
- IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "remote-file-separator");
+ this.configureFilter(builder, element, parserContext, "filter", "filename", "filter");
+ this.configureFilter(builder, element, parserContext, "mput-filter", "mput", "mputFilter");
BeanDefinition localDirExpressionDef = IntegrationNamespaceUtils
.createExpressionDefinitionFromValueOrExpression("local-directory", "local-directory-expression",
@@ -74,10 +78,11 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
return builder;
}
- protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
- String filter = element.getAttribute("filter");
- String fileNamePattern = element.getAttribute("filename-pattern");
- String fileNameRegex = element.getAttribute("filename-regex");
+ protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext,
+ String filterAttribute, String patternPrefix, String propertyName) {
+ String filter = element.getAttribute(filterAttribute);
+ String fileNamePattern = element.getAttribute(patternPrefix + "-pattern");
+ String fileNameRegex = element.getAttribute(patternPrefix + "-regex");
boolean hasFilter = StringUtils.hasText(filter);
boolean hasFileNamePattern = StringUtils.hasText(fileNamePattern);
boolean hasFileNameRegex = StringUtils.hasText(fileNameRegex);
@@ -85,23 +90,27 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
count += hasFileNamePattern ? 1 : 0;
count += hasFileNameRegex ? 1 : 0;
if (count > 1) {
- parserContext.getReaderContext().error("at most one of 'filename-pattern', " +
- "'filename-regex', or 'filter' is allowed on remote file inbound adapter", element);
+ parserContext.getReaderContext().error("at most one of '" + patternPrefix + "-pattern', " +
+ "'" + patternPrefix + "-regex', or '" + filterAttribute + "' is allowed on a remote file outbound gateway", element);
}
else if (hasFilter) {
- builder.addPropertyReference("filter", filter);
+ builder.addPropertyReference(propertyName, filter);
}
else if (hasFileNamePattern) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- this.getSimplePatternFileListFilterClassName());
+ "filter".equals(filterAttribute) ?
+ this.getSimplePatternFileListFilterClassName() :
+ SimplePatternFileListFilter.class.getName());
filterBuilder.addConstructorArgValue(fileNamePattern);
- builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
+ builder.addPropertyValue(propertyName, filterBuilder.getBeanDefinition());
}
else if (hasFileNameRegex) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- this.getRegexPatternFileListFilterClassName());
+ "filter".equals(filterAttribute) ?
+ this.getRegexPatternFileListFilterClassName() :
+ RegexPatternFileListFilter.class.getName());
filterBuilder.addConstructorArgValue(fileNameRegex);
- builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
+ builder.addPropertyValue(propertyName, filterBuilder.getBeanDefinition());
}
}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java
new file mode 100644
index 0000000000..e9ea713af8
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.file.config;
+
+import org.w3c.dom.Element;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.file.DefaultFileNameGenerator;
+import org.springframework.integration.file.remote.RemoteFileTemplate;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Oleg Zhurakousky
+ * @author Mark Fisher
+ * @author David Turanski
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public final class FileParserUtils {
+
+ private FileParserUtils() {
+ }
+
+ public static BeanDefinition parseRemoteFileTemplate(Element element, ParserContext parserContext,
+ boolean atLeastOneRemoteDirectoryAttributeRequired) {
+ BeanDefinitionBuilder templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(RemoteFileTemplate.class);
+
+ templateBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
+ // configure MessageHandler properties
+
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "temporary-file-suffix");
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "use-temporary-file-name");
+
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "auto-create-directory");
+
+ BeanDefinition expressionDef =
+ IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("remote-directory",
+ "remote-directory-expression", parserContext, element, atLeastOneRemoteDirectoryAttributeRequired);
+ if (expressionDef != null) {
+ templateBuilder.addPropertyValue("remoteDirectoryExpression", expressionDef);
+ }
+ expressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("temporary-remote-directory",
+ "temporary-remote-directory-expression", parserContext, element, false);
+ if (expressionDef != null) {
+ templateBuilder.addPropertyValue("temporaryRemoteDirectoryExpression", expressionDef);
+ }
+
+ // configure remote FileNameGenerator
+ String remoteFileNameGenerator = element.getAttribute("remote-filename-generator");
+ String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression");
+ boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
+ boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
+ if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
+ if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
+ parserContext.getReaderContext().error(
+ "at most one of 'remote-filename-generator-expression' or 'remote-filename-generator' "
+ + "is allowed on a remote file outbound adapter", element);
+ }
+ if (hasRemoteFileNameGenerator) {
+ templateBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
+ }
+ else {
+ BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder
+ .genericBeanDefinition(DefaultFileNameGenerator.class);
+ fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
+ templateBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
+ }
+ }
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "charset");
+ templateBuilder.addPropertyValue("remoteFileSeparator", element.getAttribute("remote-file-separator"));
+ return templateBuilder.getBeanDefinition();
+ }
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java
index 919c79114a..2d4faa9ea6 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java
@@ -18,19 +18,12 @@ package org.springframework.integration.file.config;
import org.w3c.dom.Element;
-import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.expression.common.LiteralExpression;
-import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
-import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
-import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
-import org.springframework.util.StringUtils;
/**
* @author Oleg Zhurakousky
@@ -45,71 +38,10 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(FileTransferringMessageHandler.class);
- handlerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
- // configure MessageHandler properties
+ BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, true);
- IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "temporary-file-suffix");
- IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "use-temporary-file-name");
-
- IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "auto-create-directory");
-
- this.configureRemoteDirectories(element, handlerBuilder);
-
- // configure remote FileNameGenerator
- String remoteFileNameGenerator = element.getAttribute("remote-filename-generator");
- String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression");
- boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
- boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
- if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
- if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
- throw new BeanDefinitionStoreException("at most one of 'remote-filename-generator-expression' or 'remote-filename-generator' " +
- "is allowed on a remote file outbound adapter");
- }
- if (hasRemoteFileNameGenerator) {
- handlerBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
- }
- else {
- BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultFileNameGenerator.class);
- fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
- handlerBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
- }
- }
- IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "charset");
- handlerBuilder.addPropertyValue("remoteFileSeparator", element.getAttribute("remote-file-separator"));
+ handlerBuilder.addConstructorArgValue(templateDefinition);
return handlerBuilder.getBeanDefinition();
}
- private void configureRemoteDirectories(Element element, BeanDefinitionBuilder handlerBuilder){
- this.doConfigureRemoteDirectory(element, handlerBuilder, "remote-directory", "remote-directory-expression", "remoteDirectoryExpression", true);
- this.doConfigureRemoteDirectory(element, handlerBuilder, "temporary-remote-directory", "temporary-remote-directory-expression", "temporaryRemoteDirectoryExpression", false);
- }
-
- private void doConfigureRemoteDirectory(Element element, BeanDefinitionBuilder handlerBuilder,
- String directoryAttribute, String directoryExpressionAttribute,
- String directoryExpressionPropertyName, boolean atLeastOneRequired){
- String remoteDirectory = element.getAttribute(directoryAttribute);
- String remoteDirectoryExpression = element.getAttribute(directoryExpressionAttribute);
- boolean hasRemoteDirectory = StringUtils.hasText(remoteDirectory);
- boolean hasRemoteDirectoryExpression = StringUtils.hasText(remoteDirectoryExpression);
- if (atLeastOneRequired){
- if (!(hasRemoteDirectory ^ hasRemoteDirectoryExpression)) {
- throw new BeanDefinitionStoreException("exactly one of '" + directoryAttribute + "' or '" + directoryExpressionAttribute + "' " +
- "is required on a remote file outbound adapter");
- }
- }
-
- BeanDefinition remoteDirectoryExpressionDefinition = null;
- if (hasRemoteDirectory) {
- remoteDirectoryExpressionDefinition = new RootBeanDefinition(LiteralExpression.class);
- remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectory);
- }
- else if (hasRemoteDirectoryExpression) {
- remoteDirectoryExpressionDefinition = new RootBeanDefinition(ExpressionFactoryBean.class);
- remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectoryExpression);
- }
- if (remoteDirectoryExpressionDefinition != null){
- handlerBuilder.addPropertyValue(directoryExpressionPropertyName, remoteDirectoryExpressionDefinition);
- }
- }
-
}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java
new file mode 100644
index 0000000000..3a44133ca7
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.file.filters;
+
+import org.springframework.integration.metadata.MetadataStore;
+import org.springframework.util.Assert;
+
+/**
+ * Stores "seen" files in a MetadataStore to survive application restarts.
+ * The default key is 'prefix' plus the absolute file name; value is the timestamp of the file.
+ * Files are deemed as already 'seen' if they exist in the store and have the
+ * same modified time as the current file.
+ *
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public abstract class AbstractPersistentAcceptOnceFileListFilter extends AbstractFileListFilter {
+
+ protected final MetadataStore store;
+
+ protected final String prefix;
+
+ private final Object monitor = new Object();
+
+ public AbstractPersistentAcceptOnceFileListFilter(MetadataStore store, String prefix) {
+ Assert.notNull(store, "'store' cannot be null");
+ Assert.notNull(prefix, "'prefix' cannot be null");
+ this.store = store;
+ this.prefix = prefix;
+ }
+
+ @Override
+ protected boolean accept(F file) {
+ String key = buildKey(file);
+ synchronized(monitor) {
+ String value = store.get(key);
+ if (value != null && isEqual(file, value)) {
+ return false;
+ }
+ store.put(key, value(file));
+ }
+ return true;
+ }
+
+ /**
+ * The default value stored for the key is the last modified date.
+ * @param file The file.
+ * @return The value to store for the file.
+ */
+ private String value(F file) {
+ return Long.toString(this.modified(file));
+ }
+
+ /**
+ * Override this method if you wish to use something other than the
+ * modified timestamp to determine equality.
+ * @param file The file.
+ * @param value The current value for the key in the store.
+ * @return true if equal.
+ */
+ protected boolean isEqual(F file, String value) {
+ return Long.valueOf(value).longValue() == this.modified(file);
+ }
+
+ /**
+ * The default key is the {@link #prefix} plus the full filename.
+ * @param file The file.
+ * @return The key.
+ */
+ protected String buildKey(F file) {
+ return this.prefix + this.fileName(file);
+ }
+
+ protected abstract long modified(F file);
+
+ protected abstract String fileName(F file);
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileSystemPersistentAcceptOnceFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileSystemPersistentAcceptOnceFileListFilter.java
new file mode 100644
index 0000000000..6fb2a2c19f
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileSystemPersistentAcceptOnceFileListFilter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.file.filters;
+
+
+import java.io.File;
+
+import org.springframework.integration.metadata.MetadataStore;
+
+/**
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public class FileSystemPersistentAcceptOnceFileListFilter extends AbstractPersistentAcceptOnceFileListFilter {
+
+ public FileSystemPersistentAcceptOnceFileListFilter(MetadataStore store, String prefix) {
+ super(store, prefix);
+ }
+
+ @Override
+ protected long modified(File file) {
+ return file.lastModified();
+ }
+
+ @Override
+ protected String fileName(File file) {
+ return file.getAbsolutePath();
+ }
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/InputStreamCallback.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/InputStreamCallback.java
new file mode 100644
index 0000000000..47bf6284af
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/InputStreamCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.file.remote;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Callback for stream-based file retrieval using a RemoteFileOperations.
+ *
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public interface InputStreamCallback {
+
+ /**
+ * Called with the InputStream for the remote file. The caller will
+ * take care of closing the stream and finalizing the file retrieval operation after
+ * this method exits.
+ *
+ * @param stream The InputStream.
+ * @throws IOException
+ */
+ void doWithInputStream(InputStream stream) throws IOException;
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java
new file mode 100644
index 0000000000..c52a5a5f61
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.file.remote;
+
+import org.springframework.messaging.Message;
+
+/**
+ * Strategy for performing operations on remote files.
+ *
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public interface RemoteFileOperations {
+
+ /**
+ * Send a file to a remote server, based on information in a message.
+ *
+ * @param message The message.
+ * @return The remote path, or null if no local file was found.
+ * @throws Exception
+ */
+ String send(Message> message);
+
+ /**
+ * Send a file to a remote server, based on information in a message.
+ * The subDirectory is appended to the remote directory evaluated from
+ * the message.
+ *
+ * @param message The message.
+ * @param subDirectory The sub directory.
+ * @return The remote path, or null if no local file was found.
+ * @throws Exception
+ */
+
+ String send(Message> message, String subDirectory);
+ /**
+ * Retrieve a remote file as an InputStream, based on information in a message.
+ *
+ * @param callback the callback.
+ * @return true if the operation was successful.
+ */
+ boolean get(Message> message, InputStreamCallback callback);
+
+ /**
+ * Remove a remote file.
+ *
+ * @param path The full path to the file.
+ * @return true when successful
+ */
+ boolean remove(String path);
+
+ /**
+ * Rename a remote file, creating directories if needed.
+ *
+ * @param fromPath The current path.
+ * @param toPath The new path.
+ */
+ void rename(String fromPath, String toPath);
+
+ /**
+ * Execute the callback's doInSession method after obtaining a session.
+ * Reliably closes the session when the method exits.
+ *
+ * @param callback the SessionCallback.
+ * @return The result of the callback method.
+ */
+ T execute(SessionCallback callback);
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java
new file mode 100644
index 0000000000..218fad0051
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.file.remote;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.expression.Expression;
+import org.springframework.integration.file.DefaultFileNameGenerator;
+import org.springframework.integration.file.FileNameGenerator;
+import org.springframework.integration.file.remote.session.Session;
+import org.springframework.integration.file.remote.session.SessionFactory;
+import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageDeliveryException;
+import org.springframework.messaging.MessagingException;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Iwein Fuld
+ * @author Mark Fisher
+ * @author Josh Long
+ * @author Oleg Zhurakousky
+ * @author David Turanski
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public class RemoteFileTemplate implements RemoteFileOperations, InitializingBean, BeanFactoryAware {
+
+ private final Log logger = LogFactory.getLog(this.getClass());
+
+ /**
+ * the {@link SessionFactory} for acquiring remote file Sessions.
+ */
+ private final SessionFactory sessionFactory;
+
+ private volatile String temporaryFileSuffix =".writing";
+
+ private volatile boolean autoCreateDirectory = false;
+
+ private volatile boolean useTemporaryFileName = true;
+
+ private volatile ExpressionEvaluatingMessageProcessor directoryExpressionProcessor;
+
+ private volatile ExpressionEvaluatingMessageProcessor temporaryDirectoryExpressionProcessor;
+
+ private volatile ExpressionEvaluatingMessageProcessor fileNameProcessor;
+
+ private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
+
+ private volatile boolean fileNameGeneratorSet;
+
+ private volatile String charset = "UTF-8";
+
+ private volatile String remoteFileSeparator = "/";
+
+ private volatile boolean hasExplicitlySetSuffix;
+
+ private volatile BeanFactory beanFactory;
+
+ public RemoteFileTemplate(SessionFactory sessionFactory) {
+ Assert.notNull(sessionFactory, "sessionFactory must not be null");
+ this.sessionFactory = sessionFactory;
+ }
+
+ public void setAutoCreateDirectory(boolean autoCreateDirectory) {
+ this.autoCreateDirectory = autoCreateDirectory;
+ }
+
+ public void setRemoteFileSeparator(String remoteFileSeparator) {
+ Assert.notNull(remoteFileSeparator, "'remoteFileSeparator' must not be null");
+ this.remoteFileSeparator = remoteFileSeparator;
+ }
+
+ public final String getRemoteFileSeparator() {
+ return remoteFileSeparator;
+ }
+
+ public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
+ Assert.notNull(remoteDirectoryExpression, "remoteDirectoryExpression must not be null");
+ this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor(remoteDirectoryExpression, String.class);
+ }
+
+ public void setTemporaryRemoteDirectoryExpression(Expression temporaryRemoteDirectoryExpression) {
+ Assert.notNull(temporaryRemoteDirectoryExpression, "temporaryRemoteDirectoryExpression must not be null");
+ this.temporaryDirectoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor(temporaryRemoteDirectoryExpression, String.class);
+ }
+
+ public void setFileNameExpression(Expression fileNameExpression) {
+ Assert.notNull(fileNameExpression, "fileNameExpression must not be null");
+ this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor(fileNameExpression, String.class);
+ }
+
+ public String getTemporaryFileSuffix() {
+ return this.temporaryFileSuffix;
+ }
+
+ public boolean isUseTemporaryFileName() {
+ return useTemporaryFileName;
+ }
+
+ public void setUseTemporaryFileName(boolean useTemporaryFileName) {
+ this.useTemporaryFileName = useTemporaryFileName;
+ }
+
+ public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
+ this.fileNameGenerator = (fileNameGenerator != null) ? fileNameGenerator : new DefaultFileNameGenerator();
+ this.fileNameGeneratorSet = fileNameGenerator != null;
+ }
+
+ public void setCharset(String charset) {
+ this.charset = charset;
+ }
+
+ public void setTemporaryFileSuffix(String temporaryFileSuffix) {
+ Assert.notNull(temporaryFileSuffix, "'temporaryFileSuffix' must not be null");
+ this.hasExplicitlySetSuffix = true;
+ this.temporaryFileSuffix = temporaryFileSuffix;
+ }
+
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ BeanFactory beanFactory = this.beanFactory;
+ if (beanFactory != null) {
+ if (this.directoryExpressionProcessor != null) {
+ this.directoryExpressionProcessor.setBeanFactory(beanFactory);
+ }
+ if (this.temporaryDirectoryExpressionProcessor != null) {
+ this.temporaryDirectoryExpressionProcessor.setBeanFactory(beanFactory);
+ }
+ if (!this.fileNameGeneratorSet && this.fileNameGenerator instanceof BeanFactoryAware) {
+ ((BeanFactoryAware) this.fileNameGenerator).setBeanFactory(beanFactory);
+ }
+ if (this.fileNameProcessor != null) {
+ this.fileNameProcessor.setBeanFactory(beanFactory);
+ }
+ }
+ if (this.autoCreateDirectory){
+ Assert.hasText(this.remoteFileSeparator, "'remoteFileSeparator' must not be empty when 'autoCreateDirectory' is set to 'true'");
+ }
+ if (hasExplicitlySetSuffix && !useTemporaryFileName){
+ this.logger.warn("Since 'use-temporary-file-name' is set to 'false' the value of 'temporary-file-suffix' has no effect");
+ }
+ }
+
+ @Override
+ public String send(final Message> message) {
+ return this.send(message, null);
+ }
+
+ @Override
+ public String send(final Message> message, final String subDirectory) {
+ Assert.notNull(this.directoryExpressionProcessor, "'remoteDirectoryExpression' is required");
+ final StreamHolder inputStreamHolder = this.payloadToInputStream(message);
+ if (inputStreamHolder != null) {
+ return this.execute(new SessionCallback() {
+
+ @Override
+ public String doInSession(Session session) throws IOException {
+ String fileName = inputStreamHolder.getName();
+ try {
+ String remoteDirectory = RemoteFileTemplate.this.directoryExpressionProcessor
+ .processMessage(message);
+ remoteDirectory = RemoteFileTemplate.this.normalizeDirectoryPath(remoteDirectory);
+ if (StringUtils.hasText(subDirectory)) {
+ if (subDirectory.startsWith(RemoteFileTemplate.this.remoteFileSeparator)) {
+ remoteDirectory += subDirectory.substring(1);
+ }
+ else {
+ remoteDirectory += RemoteFileTemplate.this.normalizeDirectoryPath(subDirectory);
+ }
+ }
+ String temporaryRemoteDirectory = remoteDirectory;
+ if (RemoteFileTemplate.this.temporaryDirectoryExpressionProcessor != null) {
+ temporaryRemoteDirectory = RemoteFileTemplate.this.temporaryDirectoryExpressionProcessor
+ .processMessage(message);
+ }
+ fileName = RemoteFileTemplate.this.fileNameGenerator.generateFileName(message);
+ RemoteFileTemplate.this.sendFileToRemoteDirectory(inputStreamHolder.getStream(),
+ temporaryRemoteDirectory, remoteDirectory, fileName, session);
+ return remoteDirectory + fileName;
+ }
+ catch (FileNotFoundException e) {
+ throw new MessageDeliveryException(message, "File [" + inputStreamHolder.getName()
+ + "] not found in local working directory; it was moved or deleted unexpectedly.", e);
+ }
+ catch (IOException e) {
+ throw new MessageDeliveryException(message, "Failed to transfer file ["
+ + inputStreamHolder.getName() + " -> " + fileName
+ + "] from local directory to remote directory.", e);
+ }
+ catch (Exception e) {
+ throw new MessageDeliveryException(message, "Error handling message for file ["
+ + inputStreamHolder.getName() + " -> " + fileName + "]", e);
+ }
+ }
+ });
+ }
+ else {
+ // A null holder means a File payload that does not exist.
+ if (logger.isWarnEnabled()) {
+ logger.warn("File " + message.getPayload() + " does not exist");
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public boolean remove(final String path) {
+ return this.execute(new SessionCallback() {
+
+ @Override
+ public Boolean doInSession(Session session) throws IOException {
+ return session.remove(path);
+ }
+ });
+ }
+
+ @Override
+ public void rename(final String fromPath, final String toPath) {
+ Assert.hasText(fromPath, "Old filename cannot be null or empty");
+ Assert.hasText(toPath, "New filename cannot be null or empty");
+
+ this.execute(new SessionCallbackWithoutResult() {
+
+ @Override
+ public void doInSessionWithoutResult(Session session) throws IOException {
+ int lastSeparator = toPath.lastIndexOf(RemoteFileTemplate.this.remoteFileSeparator);
+ if (lastSeparator > 0) {
+ String remoteFileDirectory = toPath.substring(0, lastSeparator + 1);
+ RemoteFileUtils.makeDirectories(remoteFileDirectory, session,
+ RemoteFileTemplate.this.remoteFileSeparator, RemoteFileTemplate.this.logger);
+ }
+ session.rename(fromPath, toPath);
+ }
+ });
+ }
+
+
+ @Override
+ public boolean get(final Message> message, final InputStreamCallback callback) {
+ Assert.notNull(this.fileNameProcessor, "'fileNameProcessor' needed to use get");
+ return this.execute(new SessionCallback() {
+
+ @Override
+ public Boolean doInSession(Session session) throws IOException {
+ final String remotePath = RemoteFileTemplate.this.fileNameProcessor.processMessage(message);
+ InputStream inputStream = session.readRaw(remotePath);
+ callback.doWithInputStream(inputStream);
+ inputStream.close();
+ return session.finalizeRaw();
+ }
+ });
+ }
+
+ @Override
+ public T execute(SessionCallback callback) {
+ Session session = null;
+ try {
+ session = this.sessionFactory.getSession();
+ Assert.notNull(session, "failed to acquire a Session");
+ return callback.doInSession(session);
+ }
+ catch (IOException e) {
+ throw new MessagingException("Failed to execute on session", e);
+ }
+ finally {
+ if (session != null) {
+ try {
+ session.close();
+ }
+ catch (Exception ignored) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("failed to close Session", ignored);
+ }
+ }
+ }
+ }
+ }
+
+ private StreamHolder payloadToInputStream(Message> message) throws MessageDeliveryException {
+ try {
+ Object payload = message.getPayload();
+ InputStream dataInputStream = null;
+ String name = null;
+ if (payload instanceof File) {
+ File inputFile = (File) payload;
+ if (inputFile.exists()) {
+ dataInputStream = new BufferedInputStream(new FileInputStream(inputFile));
+ name = inputFile.getAbsolutePath();
+ }
+ }
+ else if (payload instanceof byte[] || payload instanceof String) {
+ byte[] bytes = null;
+ if (payload instanceof String) {
+ bytes = ((String) payload).getBytes(this.charset);
+ name = "String payload";
+ }
+ else {
+ bytes = (byte[]) payload;
+ name = "byte[] payload";
+ }
+ dataInputStream = new ByteArrayInputStream(bytes);
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " +
+ "java.io.File, java.lang.String, and byte[]");
+ }
+ if (dataInputStream == null) {
+ return null;
+ }
+ else {
+ return new StreamHolder(dataInputStream, name);
+ }
+ }
+ catch (Exception e) {
+ throw new MessageDeliveryException(message, "Failed to create sendable file.", e);
+ }
+ }
+
+ private void sendFileToRemoteDirectory(InputStream inputStream, String temporaryRemoteDirectory,
+ String remoteDirectory, String fileName, Session session) throws IOException {
+
+ remoteDirectory = this.normalizeDirectoryPath(remoteDirectory);
+ temporaryRemoteDirectory = this.normalizeDirectoryPath(temporaryRemoteDirectory);
+
+ String remoteFilePath = remoteDirectory + fileName;
+ String tempRemoteFilePath = temporaryRemoteDirectory + fileName;
+ // write remote file first with temporary file extension if enabled
+
+ String tempFilePath = tempRemoteFilePath + (useTemporaryFileName ? this.temporaryFileSuffix : "");
+
+ if (this.autoCreateDirectory) {
+ try {
+ RemoteFileUtils.makeDirectories(remoteDirectory, session, this.remoteFileSeparator, this.logger);
+ }
+ catch (IllegalStateException e) {
+ // Revert to old FTP behavior if recursive mkdir fails, for backwards compatibility
+ session.mkdir(remoteDirectory);
+ }
+ }
+
+ try {
+ session.write(inputStream, tempFilePath);
+ // then rename it to its final name if necessary
+ if (useTemporaryFileName){
+ session.rename(tempFilePath, remoteFilePath);
+ }
+ }
+ catch (Exception e) {
+ throw new MessagingException("Failed to write to '" + tempFilePath + "' while uploading the file", e);
+ }
+ finally {
+ inputStream.close();
+ }
+ }
+
+ private String normalizeDirectoryPath(String directoryPath){
+ if (!StringUtils.hasText(directoryPath)) {
+ directoryPath = "";
+ }
+ else if (!directoryPath.endsWith(this.remoteFileSeparator)) {
+ directoryPath += this.remoteFileSeparator;
+ }
+ return directoryPath;
+ }
+
+ private class StreamHolder {
+
+ private final InputStream stream;
+
+ private final String name;
+
+ private StreamHolder(InputStream stream, String name) {
+ this.stream = stream;
+ this.name = name;
+ }
+
+ public InputStream getStream() {
+ return stream;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ }
+
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallback.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallback.java
new file mode 100644
index 0000000000..1970bb2a6d
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallback.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.file.remote;
+
+import java.io.IOException;
+
+import org.springframework.integration.file.remote.session.Session;
+
+/**
+ * Callback invoked by {@code RemoteFileOperations.execute()} - allows multiple operations
+ * on a session.
+ *
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public interface SessionCallback {
+
+ /**
+ * Called within the context of a session.
+ * Perform some operation(s) on the session. The caller will take
+ * care of closing the session after this method exits.
+ *
+ * @param session The session.
+ * @return The result of type T.
+ * @throws IOException
+ */
+ T doInSession(Session session) throws IOException;
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallbackWithoutResult.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallbackWithoutResult.java
new file mode 100644
index 0000000000..b13af2c978
--- /dev/null
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallbackWithoutResult.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.integration.file.remote;
+
+import java.io.IOException;
+
+import org.springframework.integration.file.remote.session.Session;
+
+/**
+ * Simple convenience implementation of {@link SessionCallback} for cases where
+ * no result is returned.
+ *
+ * @author Gary Russell
+ * @since 3.0
+ *
+ */
+public abstract class SessionCallbackWithoutResult implements SessionCallback {
+
+ @Override
+ public Object doInSession(Session session) throws IOException {
+ this.doInSessionWithoutResult(session);
+ return null;
+ }
+
+ /**
+ * Called within the context of a session.
+ * Perform some operation(s) on the session. The caller will take
+ * care of closing the session after this method exits.
+ *
+ * @param session The session.
+ * @throws IOException
+ */
+ protected abstract void doInSessionWithoutResult(Session session) throws IOException;
+
+}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java
index 6606a873b8..8102a7fd54 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java
@@ -38,7 +38,8 @@ import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.AbstractFileInfo;
-import org.springframework.integration.file.remote.RemoteFileUtils;
+import org.springframework.integration.file.remote.RemoteFileTemplate;
+import org.springframework.integration.file.remote.SessionCallback;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
@@ -59,7 +60,7 @@ import org.springframework.util.StringUtils;
*/
public abstract class AbstractRemoteFileOutboundGateway extends AbstractReplyProducingMessageHandler {
- protected final SessionFactory sessionFactory;
+ private final RemoteFileTemplate remoteFileTemplate;
protected final Command command;
@@ -91,7 +92,17 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
/**
* Move (rename) a remote file.
*/
- MV("mv");
+ MV("mv"),
+
+ /**
+ * Put a local file to the remote system.
+ */
+ PUT("put"),
+
+ /**
+ * Put multiple local files to the remote system.
+ */
+ MPUT("mput");
private String command;
@@ -187,25 +198,27 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
protected volatile Set options = new HashSet ();
- private volatile String remoteFileSeparator = "/";
-
private volatile Expression localDirectoryExpression;
private volatile boolean autoCreateLocalDirectory = true;
- private volatile String temporaryFileSuffix = ".writing";
-
/**
- * An {@link FileListFilter} that runs against the remote file system view.
+ * A {@link FileListFilter} that runs against the remote file system view.
*/
private volatile FileListFilter filter;
+ /**
+ * A {@link FileListFilter} that runs against the local file system view when
+ * using MPUT.
+ */
+ private volatile FileListFilter mputFilter;
private volatile Expression localFilenameGeneratorExpression;
public AbstractRemoteFileOutboundGateway(SessionFactory sessionFactory, String command,
String expression) {
- this.sessionFactory = sessionFactory;
+ Assert.notNull(sessionFactory, "'sessionFactory' cannot be null");
+ this.remoteFileTemplate = new RemoteFileTemplate(sessionFactory);
this.command = Command.toCommand(command);
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor(
new SpelExpressionParser().parseExpression(expression));
@@ -213,12 +226,30 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
public AbstractRemoteFileOutboundGateway(SessionFactory sessionFactory, Command command,
String expression) {
- this.sessionFactory = sessionFactory;
+ Assert.notNull(sessionFactory, "'sessionFactory' cannot be null");
+ this.remoteFileTemplate = new RemoteFileTemplate(sessionFactory);
this.command = command;
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor(
new SpelExpressionParser().parseExpression(expression));
}
+ public AbstractRemoteFileOutboundGateway(RemoteFileTemplate remoteFileTemplate, String command,
+ String expression) {
+ Assert.notNull(remoteFileTemplate, "'remoteFileTemplate' cannot be null");
+ this.remoteFileTemplate = remoteFileTemplate;
+ this.command = Command.toCommand(command);
+ this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor(
+ new SpelExpressionParser().parseExpression(expression));
+ }
+
+ public AbstractRemoteFileOutboundGateway(RemoteFileTemplate remoteFileTemplate, Command command,
+ String expression) {
+ Assert.notNull(remoteFileTemplate, "'remoteFileTemplate' cannot be null");
+ this.remoteFileTemplate = remoteFileTemplate;
+ this.command = command;
+ this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor(
+ new SpelExpressionParser().parseExpression(expression));
+ }
/**
* @param options the options to set
@@ -237,7 +268,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
* @param remoteFileSeparator the remoteFileSeparator to set
*/
public void setRemoteFileSeparator(String remoteFileSeparator) {
- this.remoteFileSeparator = remoteFileSeparator;
+ this.remoteFileTemplate.setRemoteFileSeparator(remoteFileSeparator);
}
/**
@@ -264,7 +295,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
* @param temporaryFileSuffix the temporaryFileSuffix to set
*/
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
- this.temporaryFileSuffix = temporaryFileSuffix;
+ this.remoteFileTemplate.setTemporaryFileSuffix(temporaryFileSuffix);
}
/**
@@ -274,6 +305,13 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
this.filter = filter;
}
+ /**
+ * @param filter the filter to set
+ */
+ public void setMputFilter(FileListFilter filter) {
+ this.mputFilter = filter;
+ }
+
public void setRenameExpression(String expression) {
Assert.notNull(expression, "'expression' cannot be null");
this.renameProcessor = new ExpressionEvaluatingMessageProcessor(
@@ -330,88 +368,105 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
if (this.getBeanFactory() != null) {
this.fileNameProcessor.setBeanFactory(this.getBeanFactory());
this.renameProcessor.setBeanFactory(this.getBeanFactory());
+ this.remoteFileTemplate.setBeanFactory(this.getBeanFactory());
}
}
@Override
protected Object handleRequestMessage(Message> requestMessage) {
- Session session = this.sessionFactory.getSession();
- try {
- switch (this.command) {
- case LS:
- return doLs(requestMessage, session);
- case GET:
- return doGet(requestMessage, session);
- case MGET:
- return doMget(requestMessage, session);
- case RM:
- return doRm(requestMessage, session);
- case MV:
- return doMv(requestMessage, session);
- default:
- return null;
- }
- }
- catch (IOException e) {
- throw new MessagingException(requestMessage, e);
- }
- finally {
- session.close();
+ switch (this.command) {
+ case LS:
+ return doLs(requestMessage);
+ case GET:
+ return doGet(requestMessage);
+ case MGET:
+ return doMget(requestMessage);
+ case RM:
+ return doRm(requestMessage);
+ case MV:
+ return doMv(requestMessage);
+ case PUT:
+ return doPut(requestMessage);
+ case MPUT:
+ return doMput(requestMessage);
+ default:
+ return null;
}
}
- private Object doLs(Message> requestMessage, Session session) throws IOException {
+ private Object doLs(Message> requestMessage) {
String dir = this.fileNameProcessor.processMessage(requestMessage);
- if (!dir.endsWith(this.remoteFileSeparator)) {
- dir += this.remoteFileSeparator;
+ if (!dir.endsWith(this.remoteFileTemplate.getRemoteFileSeparator())) {
+ dir += this.remoteFileTemplate.getRemoteFileSeparator();
}
- List> payload = ls(session, dir);
+ final String fullDir = dir;
+ List> payload = this.remoteFileTemplate.execute(new SessionCallback>() {
+
+ @Override
+ public List> doInSession(Session session) throws IOException {
+ return AbstractRemoteFileOutboundGateway.this.ls(session, fullDir);
+ }
+ });
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, dir)
.build();
}
- private Object doGet(Message> requestMessage, Session session) throws IOException {
- String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
- String remoteFilename = this.getRemoteFilename(remoteFilePath);
- String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
- File payload = this.get(requestMessage, session, remoteDir, remoteFilePath, remoteFilename, true);
+ private Object doGet(final Message> requestMessage) {
+ final String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
+ final String remoteFilename = this.getRemoteFilename(remoteFilePath);
+ final String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
+ File payload = this.remoteFileTemplate.execute(new SessionCallback() {
+
+ @Override
+ public File doInSession(Session session) throws IOException {
+ return AbstractRemoteFileOutboundGateway.this.get(requestMessage, session, remoteDir, remoteFilePath,
+ remoteFilename, true);
+
+ }
+ });
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
.build();
}
- private Object doMget(Message> requestMessage, Session session) throws IOException {
- String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
- String remoteFilename = this.getRemoteFilename(remoteFilePath);
- String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
- List payload = this.mGet(requestMessage, session, remoteDir, remoteFilename);
+ private Object doMget(final Message> requestMessage) {
+ final String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
+ final String remoteFilename = this.getRemoteFilename(remoteFilePath);
+ final String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
+ List payload = this.remoteFileTemplate.execute(new SessionCallback>() {
+
+ @Override
+ public List doInSession(Session session) throws IOException {
+ return AbstractRemoteFileOutboundGateway.this.mGet(requestMessage, session, remoteDir, remoteFilename);
+ }
+ });
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
.build();
}
- private Object doRm(Message> requestMessage, Session session) throws IOException {
- String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
+ private Object doRm(Message> requestMessage) {
+ final String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
- boolean payload = this.rm(session, remoteFilePath);
+ boolean payload = this.remoteFileTemplate.remove(remoteFilePath);
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
.build();
}
- private Object doMv(Message> requestMessage, Session session) throws IOException {
+ private Object doMv(Message> requestMessage) {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
String remoteFileNewPath = this.renameProcessor.processMessage(requestMessage);
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
- this.mv(session, remoteFilePath, remoteFileNewPath);
+ this.remoteFileTemplate.rename(remoteFilePath, remoteFileNewPath);
return MessageBuilder.withPayload(Boolean.TRUE)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
@@ -419,6 +474,66 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
.build();
}
+ private String doPut(Message> requestMessage) {
+ return this.doPut(requestMessage, null);
+ }
+
+ private String doPut(Message> requestMessage, String subDirectory) {
+ String path = this.remoteFileTemplate.send(requestMessage, subDirectory);
+ if (path == null) {
+ throw new MessagingException(requestMessage, "No local file found for " + requestMessage);
+ }
+ return path;
+ }
+
+ private Object doMput(Message> requestMessage) {
+ File file = null;
+ if (requestMessage.getPayload() instanceof File) {
+ file = (File) requestMessage.getPayload();
+ }
+ else if (requestMessage.getPayload() instanceof String) {
+ file = new File((String) requestMessage.getPayload());
+ }
+ else {
+ throw new IllegalArgumentException("Only File or String payloads allowed for 'mput'");
+ }
+ if (!file.isDirectory()) {
+ return this.doPut(requestMessage);
+ }
+ else {
+ List replies = this.putLocalDirectory(requestMessage, file, null);
+ return replies;
+ }
+ }
+
+ private List putLocalDirectory(Message> requestMessage, File file, String subDirectory) {
+ File[] files = file.listFiles();
+ List filteredFiles = this.filterMputFiles(files);
+ List replies = new ArrayList();
+ for (File filteredFile : filteredFiles) {
+ if (!filteredFile.isDirectory()) {
+ String path = this.doPut(MessageBuilder.withPayload(filteredFile)
+ .copyHeaders(requestMessage.getHeaders())
+ .build(), subDirectory);
+ if (path == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("File " + filteredFile.getAbsolutePath() + " removed before transfer; ignoring");
+ }
+ }
+ else {
+ replies.add(path);
+ }
+ }
+ else if (this.options.contains(Option.RECURSIVE)){
+ String newSubDirectory = (StringUtils.hasText(subDirectory) ?
+ subDirectory + this.remoteFileTemplate.getRemoteFileSeparator() : "")
+ + filteredFile.getName();
+ replies.addAll(this.putLocalDirectory(requestMessage, filteredFile, newSubDirectory));
+ }
+ }
+ return replies;
+ }
+
protected List> ls(Session session, String dir) throws IOException {
List lsFiles = listFilesInRemoteDir(session, dir, "");
if (!this.options.contains(Option.LINKS)) {
@@ -467,7 +582,8 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
}
}
if (recursion && this.isDirectory(file) && !(".".equals(fileName)) && !("..".equals(fileName))) {
- lsFiles.addAll(listFilesInRemoteDir(session, directory, subDirectory + fileName + this.remoteFileSeparator));
+ lsFiles.addAll(listFilesInRemoteDir(session, directory, subDirectory + fileName
+ + this.remoteFileTemplate.getRemoteFileSeparator()));
}
}
}
@@ -479,6 +595,13 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
}
+ protected final List filterMputFiles(File[] files) {
+ if (files == null) {
+ return Collections.emptyList();
+ }
+ return (this.mputFilter != null) ? this.mputFilter.filterFiles(files) : Arrays.asList(files);
+ }
+
protected void purgeLinks(List lsFiles) {
Iterator iterator = lsFiles.iterator();
while (iterator.hasNext()) {
@@ -520,7 +643,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
}
File localFile = new File(this.generateLocalDirectory(message, remoteDir), this.generateLocalFileName(message, remoteFilename));
if (!localFile.exists()) {
- String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
+ String tempFileName = localFile.getAbsolutePath() + this.remoteFileTemplate.getTemporaryFileSuffix();
File tempFile = new File(tempFileName);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
try {
@@ -583,12 +706,13 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
+ " with pattern " + remoteFilename);
}
List files = new ArrayList();
+ String remoteFileSeparator = this.remoteFileTemplate.getRemoteFileSeparator();
for (String fileName : fileNames) {
File file;
- if (fileName.contains(this.remoteFileSeparator) &&
+ if (fileName.contains(remoteFileSeparator) &&
fileName.startsWith(remoteDirectory)) { // the server returned the full path
file = this.get(message, session, remoteDirectory, fileName,
- fileName.substring(fileName.lastIndexOf(this.remoteFileSeparator)), false);
+ fileName.substring(fileName.lastIndexOf(remoteFileSeparator)), false);
}
else {
file = this.get(message, session, remoteDirectory,
@@ -626,21 +750,22 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
private String getRemoteDirectory(String remoteFilePath, String remoteFilename) {
String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf(remoteFilename));
if (remoteDir.length() == 0) {
- remoteDir = this.remoteFileSeparator;
+ remoteDir = this.remoteFileTemplate.getRemoteFileSeparator();
}
return remoteDir;
}
private String generateFullPath(String remoteDirectory, String remoteFilename) {
String path;
- if (this.remoteFileSeparator.equals(remoteDirectory)) {
+ String remoteFileSeparator = this.remoteFileTemplate.getRemoteFileSeparator();
+ if (remoteFileSeparator.equals(remoteDirectory)) {
path = remoteFilename;
}
- else if (remoteDirectory.endsWith(this.remoteFileSeparator)) {
+ else if (remoteDirectory.endsWith(remoteFileSeparator)) {
path = remoteDirectory + remoteFilename;
}
else {
- path = remoteDirectory + this.remoteFileSeparator + remoteFilename;
+ path = remoteDirectory + remoteFileSeparator + remoteFilename;
}
return path;
}
@@ -650,7 +775,7 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
*/
protected String getRemoteFilename(String remoteFilePath) {
String remoteFileName;
- int index = remoteFilePath.lastIndexOf(this.remoteFileSeparator);
+ int index = remoteFilePath.lastIndexOf(this.remoteFileTemplate.getRemoteFileSeparator());
if (index < 0) {
remoteFileName = remoteFilePath;
}
@@ -660,20 +785,6 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
return remoteFileName;
}
- protected boolean rm(Session> session, String remoteFilePath)
- throws IOException {
- return session.remove(remoteFilePath);
- }
-
- protected void mv(Session> session, String remoteFilePath, String remoteFileNewPath) throws IOException {
- int lastSeparator = remoteFileNewPath.lastIndexOf(this.remoteFileSeparator);
- if (lastSeparator > 0) {
- String remoteFileDirectory = remoteFileNewPath.substring(0, lastSeparator + 1);
- RemoteFileUtils.makeDirectories(remoteFileDirectory, session, this.remoteFileSeparator, this.logger);
- }
- session.rename(remoteFilePath, remoteFileNewPath);
- }
-
private File generateLocalDirectory(Message> message, String remoteDirectory) {
EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
evaluationContext.setVariable("remoteDirectory", remoteDirectory);
@@ -707,4 +818,5 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply
abstract protected List> asFileInfoList(Collection files);
abstract protected F enhanceNameWithSubDirectory(F file, String directory);
+
}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java
index 2610c59c35..4157fe9933 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java
@@ -16,29 +16,15 @@
package org.springframework.integration.file.remote.handler;
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.expression.Expression;
-import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
-import org.springframework.integration.file.remote.RemoteFileUtils;
-import org.springframework.integration.file.remote.session.Session;
+import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.handler.AbstractMessageHandler;
-import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.messaging.Message;
-import org.springframework.messaging.MessageDeliveryException;
-import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
/**
* A {@link org.springframework.messaging.MessageHandler} implementation that transfers files to a remote server.
@@ -53,56 +39,37 @@ import org.springframework.util.StringUtils;
*/
public class FileTransferringMessageHandler extends AbstractMessageHandler {
- private volatile String temporaryFileSuffix =".writing";
-
- private final SessionFactory sessionFactory;
-
- private volatile boolean autoCreateDirectory = false;
-
- private volatile boolean useTemporaryFileName = true;
-
- private volatile ExpressionEvaluatingMessageProcessor directoryExpressionProcessor;
-
- private volatile ExpressionEvaluatingMessageProcessor temporaryDirectoryExpressionProcessor;
-
- private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
-
- private volatile boolean fileNameGeneratorSet;
-
- private volatile String charset = "UTF-8";
-
- private volatile String remoteFileSeparator = "/";
-
- private volatile boolean hasExplicitlySetSuffix;
-
+ private final RemoteFileTemplate remoteFileTemplate;
public FileTransferringMessageHandler(SessionFactory sessionFactory) {
Assert.notNull(sessionFactory, "sessionFactory must not be null");
- this.sessionFactory = sessionFactory;
+ this.remoteFileTemplate = new RemoteFileTemplate(sessionFactory);
+ }
+
+ public FileTransferringMessageHandler(RemoteFileTemplate remoteFileTemplate) {
+ Assert.notNull(remoteFileTemplate, "remoteFileTemplate must not be null");
+ this.remoteFileTemplate = remoteFileTemplate;
}
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
- this.autoCreateDirectory = autoCreateDirectory;
+ this.remoteFileTemplate.setAutoCreateDirectory(autoCreateDirectory);
}
public void setRemoteFileSeparator(String remoteFileSeparator) {
- Assert.notNull(remoteFileSeparator, "'remoteFileSeparator' must not be null");
- this.remoteFileSeparator = remoteFileSeparator;
+ this.remoteFileTemplate.setRemoteFileSeparator(remoteFileSeparator);
}
public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
- Assert.notNull(remoteDirectoryExpression, "remoteDirectoryExpression must not be null");
- this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor(remoteDirectoryExpression, String.class);
+ this.remoteFileTemplate.setRemoteDirectoryExpression(remoteDirectoryExpression);
}
public void setTemporaryRemoteDirectoryExpression(Expression temporaryRemoteDirectoryExpression) {
- Assert.notNull(temporaryRemoteDirectoryExpression, "temporaryRemoteDirectoryExpression must not be null");
- this.temporaryDirectoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor(temporaryRemoteDirectoryExpression, String.class);
+ this.remoteFileTemplate.setTemporaryRemoteDirectoryExpression(temporaryRemoteDirectoryExpression);
}
protected String getTemporaryFileSuffix() {
- return this.temporaryFileSuffix;
+ return this.remoteFileTemplate.getTemporaryFileSuffix();
}
/**
@@ -113,198 +80,36 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
}
protected boolean isUseTemporaryFileName() {
- return useTemporaryFileName;
+ return this.remoteFileTemplate.isUseTemporaryFileName();
}
public void setUseTemporaryFileName(boolean useTemporaryFileName) {
- this.useTemporaryFileName = useTemporaryFileName;
+ this.remoteFileTemplate.setUseTemporaryFileName(useTemporaryFileName);
}
public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
- this.fileNameGenerator = (fileNameGenerator != null) ? fileNameGenerator : new DefaultFileNameGenerator();
- this.fileNameGeneratorSet = fileNameGenerator != null;
+ this.remoteFileTemplate.setFileNameGenerator(fileNameGenerator);
}
public void setCharset(String charset) {
- this.charset = charset;
+ this.remoteFileTemplate.setCharset(charset);
}
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
- Assert.notNull(temporaryFileSuffix, "'temporaryFileSuffix' must not be null");
- this.hasExplicitlySetSuffix = true;
- this.temporaryFileSuffix = temporaryFileSuffix;
+ this.remoteFileTemplate.setTemporaryFileSuffix(temporaryFileSuffix);
}
@Override
protected void onInit() throws Exception {
- Assert.notNull(this.directoryExpressionProcessor, "remoteDirectoryExpression is required");
- BeanFactory beanFactory = this.getBeanFactory();
- if (beanFactory != null) {
- this.directoryExpressionProcessor.setBeanFactory(beanFactory);
- if (this.temporaryDirectoryExpressionProcessor != null) {
- this.temporaryDirectoryExpressionProcessor.setBeanFactory(beanFactory);
- }
- if (!this.fileNameGeneratorSet && this.fileNameGenerator instanceof BeanFactoryAware) {
- ((BeanFactoryAware) this.fileNameGenerator).setBeanFactory(beanFactory);
- }
- }
- if (this.autoCreateDirectory){
- Assert.hasText(this.remoteFileSeparator, "'remoteFileSeparator' must not be empty when 'autoCreateDirectory' is set to 'true'");
- }
- if (hasExplicitlySetSuffix && !useTemporaryFileName){
- this.logger.warn("Since 'use-temporary-file-name' is set to 'false' the value of 'temporary-file-suffix' has no effect");
- }
+ this.remoteFileTemplate.setBeanFactory(this.getBeanFactory());
+ this.remoteFileTemplate.afterPropertiesSet();
}
@Override
protected void handleMessageInternal(Message> message) throws Exception {
- StreamHolder inputStreamHolder = this.payloadToInputStream(message);
- if (inputStreamHolder != null) {
- Session session = this.sessionFactory.getSession();
- String fileName = inputStreamHolder.getName();
- try {
- String remoteDirectory = this.directoryExpressionProcessor.processMessage(message);
- String temporaryRemoteDirectory = remoteDirectory;
- if (this.temporaryDirectoryExpressionProcessor != null){
- temporaryRemoteDirectory = this.temporaryDirectoryExpressionProcessor.processMessage(message);
- }
- fileName = this.fileNameGenerator.generateFileName(message);
- this.sendFileToRemoteDirectory(inputStreamHolder.getStream(), temporaryRemoteDirectory, remoteDirectory, fileName, session);
- }
- catch (FileNotFoundException e) {
- throw new MessageDeliveryException(message,
- "File [" + inputStreamHolder.getName() + "] not found in local working directory; it was moved or deleted unexpectedly.", e);
- }
- catch (IOException e) {
- throw new MessageDeliveryException(message,
- "Failed to transfer file [" + inputStreamHolder.getName() + " -> " + fileName + "] from local directory to remote directory.", e);
- }
- catch (Exception e) {
- throw new MessageDeliveryException(message,
- "Error handling message for file [" + inputStreamHolder.getName() + " -> " + fileName + "]", e);
- }
- finally {
- if (session != null) {
- session.close();
- }
- }
- }
- else {
- // A null holder means a File payload that does not exist.
- if (logger.isWarnEnabled()) {
- logger.warn("File " + message.getPayload() + " does not exist");
- }
- }
- }
-
- private StreamHolder payloadToInputStream(Message> message) throws MessageDeliveryException {
- try {
- Object payload = message.getPayload();
- InputStream dataInputStream = null;
- String name = null;
- if (payload instanceof File) {
- File inputFile = (File) payload;
- if (inputFile.exists()) {
- dataInputStream = new BufferedInputStream(new FileInputStream(inputFile));
- name = inputFile.getAbsolutePath();
- }
- }
- else if (payload instanceof byte[] || payload instanceof String) {
- byte[] bytes = null;
- if (payload instanceof String) {
- bytes = ((String) payload).getBytes(this.charset);
- name = "String payload";
- }
- else {
- bytes = (byte[]) payload;
- name = "byte[] payload";
- }
- dataInputStream = new ByteArrayInputStream(bytes);
- }
- else {
- throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " +
- "java.io.File, java.lang.String, and byte[]");
- }
- if (dataInputStream == null) {
- return null;
- }
- else {
- return new StreamHolder(dataInputStream, name);
- }
- }
- catch (Exception e) {
- throw new MessageDeliveryException(message, "Failed to create sendable file.", e);
- }
- }
-
- private void sendFileToRemoteDirectory(InputStream inputStream, String temporaryRemoteDirectory,
- String remoteDirectory, String fileName, Session session) throws FileNotFoundException, IOException {
-
- remoteDirectory = this.normalizeDirectoryPath(remoteDirectory);
- temporaryRemoteDirectory = this.normalizeDirectoryPath(temporaryRemoteDirectory);
-
- String remoteFilePath = remoteDirectory + fileName;
- String tempRemoteFilePath = temporaryRemoteDirectory + fileName;
- // write remote file first with temporary file extension if enabled
-
- String tempFilePath = tempRemoteFilePath + (useTemporaryFileName ? this.temporaryFileSuffix : "");
-
- if (this.autoCreateDirectory) {
- try {
- RemoteFileUtils.makeDirectories(remoteDirectory, session, this.remoteFileSeparator, this.logger);
- }
- catch (IllegalStateException e) {
- // Revert to old FTP behavior if recursive mkdir fails, for backwards compatibility
- session.mkdir(remoteDirectory);
- }
- }
-
- try {
- session.write(inputStream, tempFilePath);
- // then rename it to its final name if necessary
- if (useTemporaryFileName){
- session.rename(tempFilePath, remoteFilePath);
- }
- }
- catch (Exception e) {
- throw new MessagingException("Failed to write to '" + tempFilePath + "' while uploading the file", e);
- }
- finally {
- inputStream.close();
- }
- }
-
- private String normalizeDirectoryPath(String directoryPath){
- if (!StringUtils.hasText(directoryPath)) {
- directoryPath = "";
- }
- else if (!directoryPath.endsWith(this.remoteFileSeparator)) {
- directoryPath += this.remoteFileSeparator;
- }
- return directoryPath;
- }
-
- private class StreamHolder {
-
- private final InputStream stream;
-
- private final String name;
-
- private StreamHolder(InputStream stream, String name) {
- this.stream = stream;
- this.name = name;
- }
-
- public InputStream getStream() {
- return stream;
- }
-
- public String getName() {
- return name;
- }
-
+ this.remoteFileTemplate.send(message);
}
}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java
index fd20dbf108..4b243e9c01 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/synchronizer/AbstractInboundFileSynchronizer.java
@@ -33,6 +33,8 @@ import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.integration.expression.IntegrationEvaluationContextAware;
import org.springframework.integration.file.filters.FileListFilter;
+import org.springframework.integration.file.remote.RemoteFileTemplate;
+import org.springframework.integration.file.remote.SessionCallback;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.messaging.MessagingException;
@@ -59,6 +61,8 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS
protected final Log logger = LogFactory.getLog(this.getClass());
+ private final RemoteFileTemplate remoteFileTemplate;
+
private volatile EvaluationContext evaluationContext;
private volatile String remoteFileSeparator = "/";
@@ -75,11 +79,6 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS
*/
private volatile String remoteDirectory;
- /**
- * the {@link SessionFactory} for acquiring remote file Sessions.
- */
- private final SessionFactory sessionFactory;
-
/**
* An {@link FileListFilter} that runs against the remote file system view.
*/
@@ -102,7 +101,7 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS
*/
public AbstractInboundFileSynchronizer(SessionFactory sessionFactory) {
Assert.notNull(sessionFactory, "sessionFactory must not be null");
- this.sessionFactory = sessionFactory;
+ this.remoteFileTemplate = new RemoteFileTemplate(sessionFactory);
}
@@ -144,6 +143,7 @@ public abstract class AbstractInboundFileSynchronizer implements InboundFileS
this.evaluationContext = evaluationContext;
}
+ @Override
public final void afterPropertiesSet() {
Assert.notNull(this.remoteDirectory, "remoteDirectory must not be null");
Assert.notNull(this.evaluationContext, "evaluationContext must not be null");
@@ -157,36 +157,37 @@ public abstract class AbstractInboundFileSynchronizer