Merge remote-tracking branch 'upstream/master' into 4.0.0-WIP

Conflicts:
	spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
	spring-integration-core/src/main/java/org/springframework/integration/support/channel/BeanFactoryChannelResolver.java
	spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
	spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java
	spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java
	spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java
	spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java
	spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java
	spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java
	spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java
	spring-integration-http/src/test/java/org/springframework/integration/http/outbound/UriVariableExpressionTests.java
	spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/HelloWorldInterceptor.java
	spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java
	spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageGroupStoreTests.java
	spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreTests.java
	spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java

Resolved.
This commit is contained in:
Gary Russell
2013-11-25 17:59:51 -05:00
191 changed files with 8774 additions and 2707 deletions

View File

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

View File

@@ -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<String, MessageChannelWrapper> channels = new ConcurrentHashMap<String, DefaultHeaderChannelRegistry.MessageChannelWrapper>();
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<Entry<String, MessageChannelWrapper>> iterator = this.channels.entrySet().iterator();
long threshold = System.currentTimeMillis() - this.reaperDelay;
while (iterator.hasNext()) {
Entry<String, MessageChannelWrapper> 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;
}
}
}

View File

@@ -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<Resource> resources = new LinkedList<Resource>(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();
}

View File

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

View File

@@ -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<String, Class<?>> elementToTypeMap = new HashMap<String, Class<?>>();
private final static Map<String, String[][]> cannedHeaderElementExpressions = new HashMap<String, String[][]>();
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<Element> 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<String, Object> 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<Element> 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.
*/

View File

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

View File

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

View File

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

View File

@@ -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;
/**
* <p>
* An immutable {@link AbstractMap} implementation that wraps a Map<String, Expression>
* An immutable {@link AbstractMap} implementation that wraps a {@code Map<String, Object>},
* 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.
* </p>
@@ -38,16 +41,18 @@ import org.springframework.expression.Expression;
* <p>
* A {@link ExpressionEvalMapBuilder} must be used to instantiate this class
* via its {@link #from(Map)} method:
* <pre class="code">
* {@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();
*}
* </pre>
* </p>
* <p>
* Thread-safety depends on the original underlying Map.
@@ -68,11 +73,11 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
};
private final Map<String, Expression> original;
private final Map<String, ?> original;
private final EvaluationCallback evaluationCallback;
private ExpressionEvalMap(Map<String, Expression> original, EvaluationCallback evaluationCallback) {
private ExpressionEvalMap(Map<String, ?> original, EvaluationCallback evaluationCallback) {
this.original = original;
this.evaluationCallback = evaluationCallback;
}
@@ -83,8 +88,20 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
*/
@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<String, Object> {
}
@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<String, Object> {
throw new UnsupportedOperationException();
}
public static ExpressionEvalMapBuilder from(Map<String, Expression> expressions) {
@Override
public String toString() {
return this.original.toString();
}
public static ExpressionEvalMapBuilder from(Map<String, ?> expressions) {
Assert.notNull(expressions, "'expressions' must not be null.");
return new ExpressionEvalMapBuilder(expressions);
}
@@ -205,7 +228,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
*/
public static final class ExpressionEvalMapBuilder {
private final Map<String, Expression> expressions;
private final Map<String, ?> expressions;
private EvaluationCallback evaluationCallback;
@@ -219,7 +242,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
private final ExpressionEvalMapFinalBuilder finalBuilder = new ExpressionEvalMapFinalBuilderImpl();
private ExpressionEvalMapBuilder(Map<String, Expression> expressions) {
private ExpressionEvalMapBuilder(Map<String, ?> expressions) {
this.expressions = expressions;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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<Object> {
@Override
public Message<?> toMessage(Object object) throws Exception {
if (object instanceof Message<?>) {
return (Message<?>) object;

View File

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

View File

@@ -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}.
*
* <p>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<MessageChannel>, 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.
* <p>The BeanFactory to access must be set via <code>setBeanFactory</code>.
* 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.
* <p>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);
}
}
}

View File

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

View File

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

View File

@@ -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<T> 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<Class<?>, HandlerMethod> handlerMethods;
private final Map<Class<?>, HandlerMethod> handlerMessageMethods;
private final LinkedList<Map<Class<?>, HandlerMethod>> handlerMethodsList;
private final HandlerMethod handlerMethod;
private final Class<?> expectedType;
private final boolean canProcessMessageList;
@@ -154,11 +166,12 @@ public class MessagingMethodInvokerHelper<T> 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.<Class<?>, 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<T> extends AbstractExpressionEvaluator
this.expectedType = expectedType;
this.targetObject = targetObject;
this.requiresReply = expectedType != null;
this.handlerMethods = this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply);
Map<String, Map<Class<?>, HandlerMethod>> handlerMethodsForTarget =
this.findHandlerMethodsForTarget(targetObject, annotationType, methodName, requiresReply);
Map<Class<?>, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS);
Map<Class<?>, 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<Map<Class<?>, 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<T> 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<T> 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<T> extends AbstractExpressionEvaluator
}
private T processInternal(ParametersWrapper parameters) throws Exception {
Throwable evaluationException = null;
List<HandlerMethod> 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<Class<?>, HandlerMethod> findHandlerMethodsForTarget(final Object targetObject,
private Map<String, Map<Class<?>, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject,
final Class<? extends Annotation> annotationType, final String methodName, final boolean requiresReply) {
Map<String, Map<Class<?>, HandlerMethod>> handlerMethods = new HashMap<String, Map<Class<?>, HandlerMethod>>();
final Map<Class<?>, HandlerMethod> candidateMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> candidateMessageMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> fallbackMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> fallbackMessageMethods = new HashMap<Class<?>, HandlerMethod>();
final AtomicReference<Class<?>> ambiguousFallbackType = new AtomicReference<Class<?>>();
final AtomicReference<Class<?>> ambiguousFallbackMessageGenericType = new AtomicReference<Class<?>>();
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<T> 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<Method> frameworkMethods = new ArrayList<Method>();
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<T> extends AbstractExpressionEvaluator
}
if (frameworkMethods.size() == 1) {
HandlerMethod handlerMethod = new HandlerMethod(frameworkMethods.get(0), canProcessMessageList);
return Collections.<Class<?>, HandlerMethod>singletonMap(Object.class, handlerMethod);
handlerMethods.put(CANDIDATE_METHODS, Collections.<Class<?>, 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<T> extends AbstractExpressionEvaluator
return targetClass;
}
private List<HandlerMethod> 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<HandlerMethod>(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<Class<?>> candidates = this.handlerMethods.keySet();
Class<?> match = null;
if (candidates != null && !candidates.isEmpty()) {
match = ClassUtils.findClosestMatch(payloadType, candidates, true);
for (Map<Class<?>, HandlerMethod> handlerMethods : handlerMethodsList) {
Set<Class<?>> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> extends AbstractExpressionEvaluator
if (payload != null) {
return payload.getClass();
}
return Collection.class;
return this.messages.getClass();
}
}

View File

@@ -0,0 +1,2 @@
channels.autoCreate=true
messagingTemplate.lateReply.logging.level=warn

View File

@@ -1853,6 +1853,15 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="header-channels-to-string">
<xsd:annotation>
<xsd:documentation>
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.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="error-channel" type="referenceOrValueHeaderType">
<xsd:annotation>
<xsd:documentation>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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<String>("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<String>("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<String>("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));
}
}

View File

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

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
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">
<int:channel id="input" />
<int:header-enricher input-channel="input" output-channel="next">
<int:header-channels-to-string />
</int:header-enricher>
<int:transformer input-channel="next">
<bean class="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Foo" />
</int:transformer>
<int:channel id="inputPolled">
<int:queue />
</int:channel>
<int:header-enricher input-channel="inputPolled" output-channel="nextPolled">
<int:header-channels-to-string />
<int:poller fixed-delay="100" />
</int:header-enricher>
<int:transformer input-channel="nextPolled">
<bean class="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Foo" />
</int:transformer>
<int:channel id="alreadyAString">
<int:queue />
</int:channel>
<int:channel id="alreadyAnotherString">
<int:queue />
</int:channel>
<int:gateway id="gatewayNoReplyChannel"
service-interface="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Gateway"
default-request-channel="inputGateNoReplyChannel" />
<int:channel id="inputGateNoReplyChannel" />
<int:header-enricher input-channel="inputGateNoReplyChannel" output-channel="nextGateNoReplyChannel">
<int:header-channels-to-string />
</int:header-enricher>
<int:transformer input-channel="nextGateNoReplyChannel">
<bean class="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Foo" />
</int:transformer>
<int:gateway id="gatewayExplicitReplyChannel"
service-interface="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Gateway"
default-request-channel="inputGateExplicitReplyChannel" default-reply-channel="reply" />
<int:channel id="inputGateExplicitReplyChannel" />
<int:header-enricher input-channel="inputGateExplicitReplyChannel" output-channel="nextGateExplicitReplyChannel">
<int:header-channels-to-string />
</int:header-enricher>
<int:transformer input-channel="nextGateExplicitReplyChannel" output-channel="reply">
<bean class="org.springframework.integration.channel.registry.HeaderChannelRegistryTests$Foo" />
</int:transformer>
<int:channel id="reply" />
</beans>

View File

@@ -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<String>("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<String>("bar"));
assertNotNull(reply);
assertTrue(reply instanceof ErrorMessage);
}
@Test
public void testAlreadyAString() {
Message<String> 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<String> 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);
}
}

View File

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

View File

@@ -40,7 +40,7 @@
<chain input-channel="pollableInput1" output-channel="output">
<filter ref="typeSelector"/>
<poller fixed-delay="10000"/>
<poller fixed-delay="1000"/>
<service-activator ref="testHandler"/>
</chain>
@@ -49,7 +49,7 @@
<poller ref="topLevelPoller"/>
</chain>
<poller id="topLevelPoller" fixed-delay="5000"/>
<poller id="topLevelPoller" fixed-delay="1000"/>
<chain input-channel="beanInput" output-channel="output">
<beans:bean
@@ -67,7 +67,7 @@
</chain>
</chain>
<chain id="aggregatorChain2" input-channel="aggregatorInput" output-channel="output">
<chain id="aggregatorChain2" input-channel="aggregator2Input" output-channel="output">
<aggregator id="aggregatorWithinChain" ref="aggregatorBean" method="aggregate"/>
<chain id="nestedChain">
<filter id="filterWithinNestedChain" ref="typeSelector"/>

View File

@@ -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<String> log = new AtomicReference<String>();
when(logger.isWarnEnabled()).thenReturn(true);
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
log.set((String) invocation.getArguments()[0]);
return null;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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());
}

View File

@@ -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<String> receive() {
return new GenericMessage<String>(new Date().toString());
}
}
}

View File

@@ -13,11 +13,17 @@
<queue />
</channel>
<header-enricher input-channel="requests1" output-channel="requests">
<header-channels-to-string />
</header-enricher>
<channel id="requests"/>
<channel id="replies"/>
<enricher id="enricher" input-channel="input"
request-channel="requests" request-timeout="1234"
reply-timeout="9876"
request-channel="requests1" request-timeout="1234"
reply-timeout="9876" reply-channel="replies"
order="99" should-clone-payload="true" output-channel="output">
<property name="name" expression="payload.sourceName"/>
<property name="age" value="42"/>

View File

@@ -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<Expression, Expression> propertyExpressions = (Map<Expression, Expression>) accessor.getPropertyValue("propertyExpressions");
for (Map.Entry<Expression, Expression> 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")

View File

@@ -7,7 +7,7 @@
<int:service-activator input-channel="inputChannel" expression="'hello'"/>
<bean id="ChannelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<bean id="channelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<property name="autoCreate" value="true"/>
</bean>

View File

@@ -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">
<bean id="ChannelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<bean id="channelInitializer" class="org.springframework.integration.config.xml.ChannelInitializer">
<property name="autoCreate" value="true"/>
</bean>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
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">
<channel id="foo"/>
<service-activator id="fooService" input-channel="foo" output-channel="nullChannel" expression="'foo'"/>
</beans:beans>

View File

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

View File

@@ -13,7 +13,7 @@
<si:channel id="producerAndConsumerAutoStartupTestChannel"/>
<si:service-activator id="consumerEndpoint" input-channel="producerAndConsumerAutoStartupTestChannel" ref="consumer"/>
<si:service-activator id="consumerEndpoint" input-channel="producerAndConsumerAutoStartupTestChannel" ref="consumer" phase="-100"/>
<bean id="counter" class="org.springframework.integration.endpoint.ProducerAndConsumerAutoStartupTests$Counter"/>

View File

@@ -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<Object>("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<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("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<String> request) {
return request.getPayload().toUpperCase();
}
};
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
PollableChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
Message<?> test = new GenericMessage<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("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<String> request) {
return request.getPayload().toUpperCase();
}
@SuppressWarnings("unused")
public String bar(Message<String> request) {
return request.getPayload().toUpperCase();
}
};
ServiceActivatingHandler serviceActivator = new ServiceActivatingHandler(testBean);
PollableChannel outputChannel = new QueueChannel();
serviceActivator.setOutputChannel(outputChannel);
Message<?> test = new GenericMessage<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("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<Object>(new Date());
serviceActivator.handleMessage(test);
assertEquals(test, outputChannel.receive(10));
test = new GenericMessage<Object>("foo");
serviceActivator.handleMessage(test);
assertNotEquals("FOO", outputChannel.receive(10).getPayload());
}
@SuppressWarnings("unused")
private static class SingleAnnotationTestBean {

View File

@@ -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<Number> 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<Object>(2L)));
assertEquals("1", helper.process(new GenericMessage<Object>(1)));
assertEquals("foo", helper.process(new GenericMessage<Object>(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<String> message) {
return message.getPayload();
}
public Integer m2(Message<Integer> 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<Object>("foo")));
assertEquals(1, helper.process(new GenericMessage<Object>(1)));
assertEquals(targetObject, helper.process(new GenericMessage<Object>(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<Object>("foo")));
assertEquals(1, helper.process(new GenericMessage<Object>(1)));
assertEquals(targetObject, helper.process(new GenericMessage<Object>(targetObject)));
}
@Test
public void testInt3199PrecedenceOfCandidates() throws Exception {
class Foo {
public Object m1(Message<String> 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<Object>("foo")));
assertEquals("FOO", helper.process(new GenericMessage<Object>(targetObject)));
}
private static class ExceptionCauseMatcher extends TypeSafeMatcher<Exception> {
private Throwable cause;
@@ -528,5 +662,7 @@ public class MethodInvokingMessageProcessorTests {
this.lastArg = s;
return s;
}
}
}

View File

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

View File

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

View File

@@ -171,7 +171,7 @@ public class SpelTransformerIntegrationTests {
@Override
public Class<?>[] getSpecificTargetClasses() {
return new Class[] {Foo.class};
return new Class<?>[] {Foo.class};
}
@Override

View File

@@ -0,0 +1,2 @@
#channels.autoCreate=false
messagingTemplate.lateReply.logging.level=error

View File

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

View File

@@ -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<ApplicationEvent>() {
@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<ApplicationEvent>() {
@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<ApplicationEvent>() {
@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<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
Object source = event.getSource();
if (source instanceof Message){

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,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)) {

View File

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

View File

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

View File

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

View File

@@ -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<F> extends AbstractFileListFilter<F> {
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);
}

View File

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

View File

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

View File

@@ -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<F> {
/**
* 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> T execute(SessionCallback<F, T> callback);
}

View File

@@ -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<F> implements RemoteFileOperations<F>, InitializingBean, BeanFactoryAware {
private final Log logger = LogFactory.getLog(this.getClass());
/**
* the {@link SessionFactory} for acquiring remote file Sessions.
*/
private final SessionFactory<F> sessionFactory;
private volatile String temporaryFileSuffix =".writing";
private volatile boolean autoCreateDirectory = false;
private volatile boolean useTemporaryFileName = true;
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> 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<F> 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<String>(remoteDirectoryExpression, String.class);
}
public void setTemporaryRemoteDirectoryExpression(Expression temporaryRemoteDirectoryExpression) {
Assert.notNull(temporaryRemoteDirectoryExpression, "temporaryRemoteDirectoryExpression must not be null");
this.temporaryDirectoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(temporaryRemoteDirectoryExpression, String.class);
}
public void setFileNameExpression(Expression fileNameExpression) {
Assert.notNull(fileNameExpression, "fileNameExpression must not be null");
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(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<F, String>() {
@Override
public String doInSession(Session<F> 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<F, Boolean>() {
@Override
public Boolean doInSession(Session<F> 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<F>() {
@Override
public void doInSessionWithoutResult(Session<F> 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<F, Boolean>() {
@Override
public Boolean doInSession(Session<F> 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> T execute(SessionCallback<F, T> callback) {
Session<F> 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<F> 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;
}
}
}

View File

@@ -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<F, T> {
/**
* 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<F> session) throws IOException;
}

View File

@@ -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<F> implements SessionCallback<F, Object> {
@Override
public Object doInSession(Session<F> 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<F> session) throws IOException;
}

View File

@@ -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<F> extends AbstractReplyProducingMessageHandler {
protected final SessionFactory<F> sessionFactory;
private final RemoteFileTemplate<F> remoteFileTemplate;
protected final Command command;
@@ -91,7 +92,17 @@ public abstract class AbstractRemoteFileOutboundGateway<F> 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<F> extends AbstractReply
protected volatile Set<Option> options = new HashSet<Option>();
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 <em>remote</em> file system view.
* A {@link FileListFilter} that runs against the <em>remote</em> file system view.
*/
private volatile FileListFilter<F> filter;
/**
* A {@link FileListFilter} that runs against the <em>local</em> file system view when
* using MPUT.
*/
private volatile FileListFilter<File> mputFilter;
private volatile Expression localFilenameGeneratorExpression;
public AbstractRemoteFileOutboundGateway(SessionFactory<F> sessionFactory, String command,
String expression) {
this.sessionFactory = sessionFactory;
Assert.notNull(sessionFactory, "'sessionFactory' cannot be null");
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
this.command = Command.toCommand(command);
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression(expression));
@@ -213,12 +226,30 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
public AbstractRemoteFileOutboundGateway(SessionFactory<F> sessionFactory, Command command,
String expression) {
this.sessionFactory = sessionFactory;
Assert.notNull(sessionFactory, "'sessionFactory' cannot be null");
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
this.command = command;
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression(expression));
}
public AbstractRemoteFileOutboundGateway(RemoteFileTemplate<F> remoteFileTemplate, String command,
String expression) {
Assert.notNull(remoteFileTemplate, "'remoteFileTemplate' cannot be null");
this.remoteFileTemplate = remoteFileTemplate;
this.command = Command.toCommand(command);
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression(expression));
}
public AbstractRemoteFileOutboundGateway(RemoteFileTemplate<F> remoteFileTemplate, Command command,
String expression) {
Assert.notNull(remoteFileTemplate, "'remoteFileTemplate' cannot be null");
this.remoteFileTemplate = remoteFileTemplate;
this.command = command;
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression(expression));
}
/**
* @param options the options to set
@@ -237,7 +268,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> 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<F> 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<F> extends AbstractReply
this.filter = filter;
}
/**
* @param filter the filter to set
*/
public void setMputFilter(FileListFilter<File> filter) {
this.mputFilter = filter;
}
public void setRenameExpression(String expression) {
Assert.notNull(expression, "'expression' cannot be null");
this.renameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
@@ -330,88 +368,105 @@ public abstract class AbstractRemoteFileOutboundGateway<F> 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<F> 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<F> 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<F, List<?>>() {
@Override
public List<?> doInSession(Session<F> 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<F> 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<F, File>() {
@Override
public File doInSession(Session<F> 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<F> session) throws IOException {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
List<File> 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<File> payload = this.remoteFileTemplate.execute(new SessionCallback<F, List<File>>() {
@Override
public List<File> doInSession(Session<F> 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<F> 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<F> 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<F> 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<String> replies = this.putLocalDirectory(requestMessage, file, null);
return replies;
}
}
private List<String> putLocalDirectory(Message<?> requestMessage, File file, String subDirectory) {
File[] files = file.listFiles();
List<File> filteredFiles = this.filterMputFiles(files);
List<String> replies = new ArrayList<String>();
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<F> session, String dir) throws IOException {
List<F> lsFiles = listFilesInRemoteDir(session, dir, "");
if (!this.options.contains(Option.LINKS)) {
@@ -467,7 +582,8 @@ public abstract class AbstractRemoteFileOutboundGateway<F> 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<F> extends AbstractReply
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
}
protected final List<File> filterMputFiles(File[] files) {
if (files == null) {
return Collections.emptyList();
}
return (this.mputFilter != null) ? this.mputFilter.filterFiles(files) : Arrays.asList(files);
}
protected void purgeLinks(List<F> lsFiles) {
Iterator<F> iterator = lsFiles.iterator();
while (iterator.hasNext()) {
@@ -520,7 +643,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> 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<F> extends AbstractReply
+ " with pattern " + remoteFilename);
}
List<File> files = new ArrayList<File>();
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<F> 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<F> 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<F> 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<F> extends AbstractReply
abstract protected List<AbstractFileInfo<F>> asFileInfoList(Collection<F> files);
abstract protected F enhanceNameWithSubDirectory(F file, String directory);
}

View File

@@ -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<F> extends AbstractMessageHandler {
private volatile String temporaryFileSuffix =".writing";
private final SessionFactory<F> sessionFactory;
private volatile boolean autoCreateDirectory = false;
private volatile boolean useTemporaryFileName = true;
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> 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<F> remoteFileTemplate;
public FileTransferringMessageHandler(SessionFactory<F> sessionFactory) {
Assert.notNull(sessionFactory, "sessionFactory must not be null");
this.sessionFactory = sessionFactory;
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
}
public FileTransferringMessageHandler(RemoteFileTemplate<F> 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<String>(remoteDirectoryExpression, String.class);
this.remoteFileTemplate.setRemoteDirectoryExpression(remoteDirectoryExpression);
}
public void setTemporaryRemoteDirectoryExpression(Expression temporaryRemoteDirectoryExpression) {
Assert.notNull(temporaryRemoteDirectoryExpression, "temporaryRemoteDirectoryExpression must not be null");
this.temporaryDirectoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(temporaryRemoteDirectoryExpression, String.class);
this.remoteFileTemplate.setTemporaryRemoteDirectoryExpression(temporaryRemoteDirectoryExpression);
}
protected String getTemporaryFileSuffix() {
return this.temporaryFileSuffix;
return this.remoteFileTemplate.getTemporaryFileSuffix();
}
/**
@@ -113,198 +80,36 @@ public class FileTransferringMessageHandler<F> 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<F> 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<F> 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);
}
}

View File

@@ -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<F> implements InboundFileS
protected final Log logger = LogFactory.getLog(this.getClass());
private final RemoteFileTemplate<F> remoteFileTemplate;
private volatile EvaluationContext evaluationContext;
private volatile String remoteFileSeparator = "/";
@@ -75,11 +79,6 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
*/
private volatile String remoteDirectory;
/**
* the {@link SessionFactory} for acquiring remote file Sessions.
*/
private final SessionFactory<F> sessionFactory;
/**
* An {@link FileListFilter} that runs against the <em>remote</em> file system view.
*/
@@ -102,7 +101,7 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
*/
public AbstractInboundFileSynchronizer(SessionFactory<F> sessionFactory) {
Assert.notNull(sessionFactory, "sessionFactory must not be null");
this.sessionFactory = sessionFactory;
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
}
@@ -144,6 +143,7 @@ public abstract class AbstractInboundFileSynchronizer<F> 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<F> implements InboundFileS
return temporaryFileSuffix;
}
public void synchronizeToLocalDirectory(File localDirectory) {
Session<F> session = null;
@Override
public void synchronizeToLocalDirectory(final File localDirectory) {
try {
session = this.sessionFactory.getSession();
Assert.notNull(session, "failed to acquire a Session");
F[] files = session.list(this.remoteDirectory);
if (!ObjectUtils.isEmpty(files)) {
Collection<F> filteredFiles = this.filterFiles(files);
for (F file : filteredFiles) {
if (file != null) {
this.copyFileToLocalDirectory(this.remoteDirectory, file, localDirectory, session);
int transferred = this.remoteFileTemplate.execute(new SessionCallback<F, Integer>() {
@Override
public Integer doInSession(Session<F> session) throws IOException {
F[] files = session.list(AbstractInboundFileSynchronizer.this.remoteDirectory);
if (!ObjectUtils.isEmpty(files)) {
Collection<F> filteredFiles = AbstractInboundFileSynchronizer.this.filterFiles(files);
for (F file : filteredFiles) {
if (file != null) {
AbstractInboundFileSynchronizer.this.copyFileToLocalDirectory(
AbstractInboundFileSynchronizer.this.remoteDirectory, file, localDirectory,
session);
}
}
return filteredFiles.size();
}
else {
return 0;
}
}
});
if (logger.isDebugEnabled()) {
logger.debug(transferred + " files transferred");
}
}
catch (IOException e) {
catch (Exception e) {
throw new MessagingException("Problem occurred while synchronizing remote to local directory", e);
}
finally {
if (session != null) {
try {
session.close();
}
catch (Exception ignored) {
if (logger.isDebugEnabled()) {
logger.debug("failed to close Session", ignored);
}
}
}
}
}
private void copyFileToLocalDirectory(String remoteDirectoryPath, F remoteFile, File localDirectory, Session<F> session) throws IOException {

View File

@@ -645,7 +645,79 @@ Only files matching this regular expression will be picked up by this adapter.
<xsd:enumeration value="rm"/>
<xsd:enumeration value="mget"/>
<xsd:enumeration value="mv"/>
<xsd:enumeration value="put"/>
<xsd:enumeration value="mput"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:attributeGroup name="remoteOutboundAttributeGroup">
<xsd:attribute name="remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the directory
path where the files will be transferred to
(e.g., "headers.['remote_dir'] +
'/myTransfers'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="temporary-remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the temporary directory
path where files will be transferred to before they are moved to the remote-directory
(e.g., "headers.['remote_dir'] +
'/temp/myTransfers'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-create-directory" type="xsd:string"
default="false">
<xsd:annotation>
<xsd:documentation>
Specify whether to automatically create the
remote target directory if
it doesn't exist.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="remote-filename-generator" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to specify a reference to
[org.springframework.integration.file.FileNameGenerator] bean.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.integration.file.FileNameGenerator" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="remote-filename-generator-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide SpEL expression which
will compute file name of
the remote file (e.g., assuming payload
is java.io.File
"payload.getName() + '.transfered'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="use-temporary-file-name" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
Allows you to suppress using a temporary file name while writing the file.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
</xsd:schema>

View File

@@ -122,6 +122,17 @@ public class DefaultFileNameGeneratorTests {
assertEquals("bar", filename);
}
@Test
public void customExpressionTakesPrecedenceOverFilePayload() {
DefaultFileNameGenerator generator = new DefaultFileNameGenerator();
generator.setBeanFactory(mock(BeanFactory.class));
generator.setExpression("'foobar'");
File payload = new File("/some/path/ignore");
Message<?> message = MessageBuilder.withPayload(payload).build();
String filename = generator.generateFileName(message);
assertEquals("foobar", filename);
}
@Test
public void customHeaderNameTakesPrecedenceOverDefault() {
DefaultFileNameGenerator generator = new DefaultFileNameGenerator();

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- under test -->
<bean id="pollableFileSource" class="org.springframework.integration.file.FileReadingMessageSource"
p:directory="file:${java.io.tmpdir}/FileReadingMessageSourcePersistentFilterIntegrationTests"
p:filter-ref="persistentFilter"/>
<!-- persistent filter -->
<bean id="persistentFilter" class="org.springframework.integration.file.filters.FileSystemPersistentAcceptOnceFileListFilter">
<constructor-arg ref="ppms" />
<constructor-arg value="frmsPersistTest" />
</bean>
<bean id="ppms" class="org.springframework.integration.metadata.PropertiesPersistingMetadataStore">
<property name="baseDirectory"
value="#{T(System).getProperty('java.io.tmpdir') + T(java.io.File).separator + 'FileReadingMessageSourcePersistentFilterIntegrationTests.meta'}"/>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />
</beans>

View File

@@ -0,0 +1,126 @@
/*
* 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;
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 java.io.File;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
/**
* @author Iwein Fuld
* @author Gary Russell
*/
public class FileReadingMessageSourcePersistentFilterIntegrationTests {
AbstractApplicationContext context;
FileReadingMessageSource pollableFileSource;
private static File inputDir;
@AfterClass
public static void cleanUp() throws Throwable {
if(inputDir.exists()) {
inputDir.delete();
}
}
@BeforeClass
public static void setupInputDir() {
inputDir = new File(System.getProperty("java.io.tmpdir") + "/"
+ FileReadingMessageSourcePersistentFilterIntegrationTests.class.getSimpleName());
inputDir.mkdir();
}
@Before
public void generateTestFiles() throws Exception {
File.createTempFile("test", null, inputDir).setLastModified(System.currentTimeMillis() - 1000);
File.createTempFile("test", null, inputDir).setLastModified(System.currentTimeMillis() - 1000);
File.createTempFile("test", null, inputDir).setLastModified(System.currentTimeMillis() - 1000);
this.loadContextAndGetMessageSource();
}
private void loadContextAndGetMessageSource() {
this.context = new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-context.xml",
this.getClass());
this.pollableFileSource = context.getBean(FileReadingMessageSource.class);
}
@After
public void cleanoutInputDir() throws Exception {
File[] listFiles = inputDir.listFiles();
for (int i = 0; i < listFiles.length; i++) {
listFiles[i].delete();
}
}
@AfterClass
public static void removeInputDir() throws Exception {
inputDir.delete();
File persistDir = new File(System.getProperty("java.io.tmpdir") + "/"
+ FileReadingMessageSourcePersistentFilterIntegrationTests.class.getSimpleName()
+ ".meta");
File persist = new File(persistDir, "metadata-store.properties");
persist.delete();
persistDir.delete();
}
@Test
public void configured() throws Exception {
DirectFieldAccessor accessor = new DirectFieldAccessor(pollableFileSource);
assertEquals(inputDir, accessor.getPropertyValue("directory"));
}
@Test
public void getFiles() throws Exception {
Message<File> received1 = pollableFileSource.receive();
System.out.println("receive files round 1");
assertNotNull("This should return the first message", received1);
pollableFileSource.onSend(received1);
Message<File> received2 = pollableFileSource.receive();
assertNotNull(received2);
pollableFileSource.onSend(received2);
Message<File> received3 = pollableFileSource.receive();
assertNotNull(received3);
pollableFileSource.onSend(received3);
assertNotSame(received1 + " == " + received2, received1.getPayload(), received2.getPayload());
assertNotSame(received1 + " == " + received3, received1.getPayload(), received3.getPayload());
assertNotSame(received2 + " == " + received3, received2.getPayload(), received3.getPayload());
this.context.destroy();
this.loadContextAndGetMessageSource();
Message<File> received4 = pollableFileSource.receive();
assertNull(received4);
this.context.destroy();
}
}

View File

@@ -106,9 +106,9 @@ public class FileOutboundChannelAdapterParserTests {
assertThat(actual, is(expected));
DefaultFileNameGenerator fileNameGenerator = (DefaultFileNameGenerator) handlerAccessor.getPropertyValue("fileNameGenerator");
assertNotNull(fileNameGenerator);
String expression = (String) TestUtils.getPropertyValue(fileNameGenerator, "expression");
Expression expression = TestUtils.getPropertyValue(fileNameGenerator, "expression", Expression.class);
assertNotNull(expression);
assertEquals("'foo.txt'", expression);
assertEquals("'foo.txt'", expression.getExpressionString());
assertEquals(Boolean.FALSE, handlerAccessor.getPropertyValue("deleteSourceFiles"));
}

View File

@@ -26,6 +26,7 @@ import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -93,9 +94,9 @@ public class FileOutboundGatewayParserTests {
assertEquals(Boolean.TRUE, handlerAccessor.getPropertyValue("requiresReply"));
DefaultFileNameGenerator fileNameGenerator = (DefaultFileNameGenerator) handlerAccessor.getPropertyValue("fileNameGenerator");
assertNotNull(fileNameGenerator);
String expression = (String) TestUtils.getPropertyValue(fileNameGenerator, "expression");
Expression expression = TestUtils.getPropertyValue(fileNameGenerator, "expression", Expression.class);
assertNotNull(expression);
assertEquals("'foo.txt'", expression);
assertEquals("'foo.txt'", expression.getExpressionString());
Long sendTimeout = TestUtils.getPropertyValue(handler, "messagingTemplate.sendTimeout", Long.class);
assertEquals(Long.valueOf(777), sendTimeout);

View File

@@ -0,0 +1,47 @@
/*
* 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 static org.junit.Assert.assertTrue;
import java.io.File;
import org.junit.Test;
import org.springframework.integration.metadata.MetadataStore;
import org.springframework.integration.metadata.SimpleMetadataStore;
/**
* @author Gary Russell
* @since 3.0
*
*/
public class PersistentAcceptOnceFileListFilterTests {
@Test
public void testFileSystem() throws Exception {
MetadataStore store = new SimpleMetadataStore();
FileSystemPersistentAcceptOnceFileListFilter filter = new FileSystemPersistentAcceptOnceFileListFilter(store, "foo:");
File file = File.createTempFile("foo", ".txt");
assertTrue(filter.filterFiles(new File[] {file}).size() == 1);
assertTrue(filter.filterFiles(new File[] {file}).size() == 0);
file.setLastModified(27L);
assertTrue(filter.filterFiles(new File[] {file}).size() == 1);
assertTrue(filter.filterFiles(new File[] {file}).size() == 0);
file.delete();
}
}

View File

@@ -15,10 +15,15 @@
*/
package org.springframework.integration.file.remote.gateway;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -36,20 +41,25 @@ import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter;
import org.springframework.integration.file.remote.AbstractFileInfo;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.GenericMessage;
/**
@@ -61,6 +71,9 @@ public class RemoteFileOutboundGatewayTests {
private final String tmpDir = System.getProperty("java.io.tmpdir");
@Rule
public final TemporaryFolder tempFolder = new TemporaryFolder();
@Test(expected = IllegalArgumentException.class)
public void testBad() throws Exception {
@@ -987,6 +1000,120 @@ public class RemoteFileOutboundGatewayTests {
out.getHeaders().get(FileHeaders.REMOTE_FILE));
}
@Test
public void testPut() throws Exception {
@SuppressWarnings("unchecked")
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
@SuppressWarnings("unchecked")
Session<TestLsEntry> session = mock(Session.class);
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
template.setBeanFactory(mock(BeanFactory.class));
template.afterPropertiesSet();
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "put", null);
FileTransferringMessageHandler<TestLsEntry> handler = new FileTransferringMessageHandler<TestLsEntry>(sessionFactory);
handler.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
gw.afterPropertiesSet();
when(sessionFactory.getSession()).thenReturn(session);
final AtomicReference<String> written = new AtomicReference<String>();
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
written.set((String) invocation.getArguments()[1]);
return null;
}
}).when(session).write(any(InputStream.class), anyString());
Message<String> requestMessage = MessageBuilder.withPayload("hello")
.setHeader(FileHeaders.FILENAME, "bar.txt")
.build();
String path = (String) gw.handleRequestMessage(requestMessage);
assertEquals("foo/bar.txt", path);
verify(session).rename("foo/bar.txt.writing", "foo/bar.txt");
}
@Test
public void testMput() throws Exception {
@SuppressWarnings("unchecked")
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
@SuppressWarnings("unchecked")
Session<TestLsEntry> session = mock(Session.class);
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
template.setBeanFactory(mock(BeanFactory.class));
template.afterPropertiesSet();
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "mput", null);
gw.afterPropertiesSet();
when(sessionFactory.getSession()).thenReturn(session);
final AtomicReference<String> written = new AtomicReference<String>();
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
written.set((String) invocation.getArguments()[1]);
return null;
}
}).when(session).write(any(InputStream.class), anyString());
tempFolder.newFile("baz.txt");
tempFolder.newFile("qux.txt");
Message<File> requestMessage = MessageBuilder.withPayload(tempFolder.getRoot())
.build();
@SuppressWarnings("unchecked")
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
assertEquals(2, out.size());
assertThat(out.get(0),
not(equalTo(out.get(1))));
assertThat(out.get(0), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt")));
assertThat(out.get(1), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt")));
}
@Test
public void testMputRecursive() throws Exception {
@SuppressWarnings("unchecked")
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
@SuppressWarnings("unchecked")
Session<TestLsEntry> session = mock(Session.class);
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
template.setBeanFactory(mock(BeanFactory.class));
template.afterPropertiesSet();
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "mput", null);
gw.setOptions("-R");
gw.afterPropertiesSet();
when(sessionFactory.getSession()).thenReturn(session);
final AtomicReference<String> written = new AtomicReference<String>();
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
written.set((String) invocation.getArguments()[1]);
return null;
}
}).when(session).write(any(InputStream.class), anyString());
tempFolder.newFile("baz.txt");
tempFolder.newFile("qux.txt");
File dir1 = tempFolder.newFolder();
File file3 = File.createTempFile("foo", ".txt", dir1);
Message<File> requestMessage = MessageBuilder.withPayload(tempFolder.getRoot())
.build();
@SuppressWarnings("unchecked")
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
assertEquals(3, out.size());
assertThat(out.get(0),
not(equalTo(out.get(1))));
assertThat(out.get(0), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
assertThat(out.get(1), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
assertThat(out.get(2), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
}
}
class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<TestLsEntry> {
@@ -998,6 +1125,13 @@ class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<Te
this.setBeanFactory(mock(BeanFactory.class));
}
public TestRemoteFileOutboundGateway(RemoteFileTemplate<TestLsEntry> remoteFileTemplate, String command,
String expression) {
super(remoteFileTemplate, command, expression);
this.setBeanFactory(mock(BeanFactory.class));
}
@Override
protected boolean isDirectory(TestLsEntry file) {
return file.isDirectory();

View File

@@ -0,0 +1,49 @@
/*
* 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.ftp.filters;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter;
import org.springframework.integration.metadata.MetadataStore;
/**
* Since the super class deems files as 'not seen' if the timestamp is different, remote file
* users should use the adapter's preserve-timestamp option. Otherwise if a file is re-fetched
* it will have a new timestamp.
*
* @author Gary Russell
* @since 3.0
*
*/
public class FtpPersistentAcceptOnceFileListFilter extends AbstractPersistentAcceptOnceFileListFilter<FTPFile> {
public FtpPersistentAcceptOnceFileListFilter(MetadataStore store, String prefix) {
super(store, prefix);
}
@Override
protected long modified(FTPFile file) {
return file.getTimestamp().getTimeInMillis();
}
@Override
protected String fileName(FTPFile file) {
return file.getName();
}
}

View File

@@ -23,6 +23,7 @@ import java.util.List;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.remote.AbstractFileInfo;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.ftp.session.FtpFileInfo;
@@ -40,6 +41,11 @@ public class FtpOutboundGateway extends AbstractRemoteFileOutboundGateway<FTPFil
super(sessionFactory, command, expression);
}
private FtpOutboundGateway(RemoteFileTemplate<FTPFile> remoteFileTemplate, String command, String expression) {
super(remoteFileTemplate, command, expression);
}
@Override
protected boolean isDirectory(FTPFile file) {
return file.isDirectory();

View File

@@ -26,73 +26,6 @@
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the directory
path where the files will be transferred to
(e.g., "headers.['remote_dir'] +
'/myTransfers'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="temporary-remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the temporary directory
path where files will be transferred to before they are moved to the remote-directory
(e.g., "headers.['remote_dir'] +
'/temp/myTransfers'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-create-directory" type="xsd:string"
default="false">
<xsd:annotation>
<xsd:documentation>
Specify whether to automatically create the
remote target directory if
it doesn't exist.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="remote-filename-generator" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to specify a reference to
[org.springframework.integration.file.FileNameGenerator] bean.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.integration.file.FileNameGenerator" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="remote-filename-generator-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide SpEL expression which
will compute file name of
the remote file (e.g., assuming payload
is java.io.File
"payload.getName() + '.transfered'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="use-temporary-file-name" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
Allows you to suppress using a temporary file name while writing the file.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -113,6 +46,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -268,7 +202,7 @@
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="base-adapter-type">
<xsd:extension base="base-ftp-adapter-type">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
@@ -379,7 +313,9 @@
<xsd:documentation>
Allows you to specify a reference to
[org.springframework.integration.file.filters.FileListFilter]
bean.
bean. This filter acts against the remote server view when using the 'ls'
or 'mget' commands.
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -387,10 +323,11 @@
<xsd:annotation>
<xsd:documentation>
Allows you to provide file name pattern to
determine the file names retrieved by the ls command
determine the file names retrieved by the 'ls' and 'mget' commands
and is based
on simple pattern matching algorithm (e.g., "*.txt, fo*.txt"
etc.)
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -398,9 +335,49 @@
<xsd:annotation>
<xsd:documentation>
Allows you to provide Regular Expression to
determine the file names retrieved by the ls command.
determine the file names retrieved by the 'ls' and 'mget' commands.
(e.g.,
"f[o]+\.txt" etc.)
Only one of 'filter', 'filename-pattern', or 'filename-regex' is allowed.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mput-filter" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.integration.file.filters.FileListFilter" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
Allows you to specify a reference to
[org.springframework.integration.file.filters.FileListFilter]
bean. This filter acts on the local file system when using the 'mput' command.
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mput-pattern" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide file name pattern to
determine the file names sent by the 'mput' command
and is based
on simple pattern matching algorithm (e.g., "*.txt, fo*.txt"
etc.)
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mput-regex" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide Regular Expression to
determine the file names sent by the 'mput' command
(e.g.,
"f[o]+\.txt" etc.)
Only one of 'mput-filter', 'mput-pattern', or 'mput-regex' is allowed.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -486,6 +463,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="int-file:remoteOutboundAttributeGroup" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -101,7 +101,7 @@ public class TesFtpServer {
fos.close();
targetFtpDirectory = new File(ftpRootFolder, "ftpTarget");
targetFtpDirectory.mkdirs();
targetFtpDirectory.mkdir();
}
};
this.localFolder = new TemporaryFolder() {
@@ -123,7 +123,7 @@ public class TesFtpServer {
file.createNewFile();
targetLocalDirectory = new File(rootFolder, "localTarget");
targetLocalDirectory.mkdirs();
targetLocalDirectory.mkdir();
}
};
}
@@ -183,14 +183,16 @@ public class TesFtpServer {
}
public static void recursiveDelete(File file) {
public void recursiveDelete(File file) {
File[] files = file.listFiles();
if (files != null) {
for (File each : files) {
recursiveDelete(each);
}
}
file.delete();
if (!(file.equals(this.targetFtpDirectory) || file.equals(this.targetLocalDirectory))) {
file.delete();
}
}

View File

@@ -84,7 +84,7 @@ public class FtpInboundChannelAdapterParserTests {
assertEquals("", remoteFileSeparator);
FtpSimplePatternFileListFilter filter = (FtpSimplePatternFileListFilter) TestUtils.getPropertyValue(fisync, "filter");
assertNotNull(filter);
Object sessionFactory = TestUtils.getPropertyValue(fisync, "sessionFactory");
Object sessionFactory = TestUtils.getPropertyValue(fisync, "remoteFileTemplate.sessionFactory");
assertTrue(DefaultFtpSessionFactory.class.isAssignableFrom(sessionFactory.getClass()));
FileListFilter<?> acceptAllFilter = ac.getBean("acceptAllFilter", FileListFilter.class);
assertTrue(TestUtils.getPropertyValue(inbound, "fileSource.scanner.filter.fileFilters", Collection.class).contains(acceptAllFilter));
@@ -107,7 +107,7 @@ public class FtpInboundChannelAdapterParserTests {
ApplicationContext ac = new ClassPathXmlApplicationContext(
"FtpInboundChannelAdapterParserTests-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("simpleAdapterWithCachedSessions", SourcePollingChannelAdapter.class);
Object sessionFactory = TestUtils.getPropertyValue(adapter, "source.synchronizer.sessionFactory");
Object sessionFactory = TestUtils.getPropertyValue(adapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
assertEquals(CachingSessionFactory.class, sessionFactory.getClass());
FtpInboundFileSynchronizer fisync =
TestUtils.getPropertyValue(adapter, "source.synchronizer", FtpInboundFileSynchronizer.class);
@@ -142,6 +142,7 @@ public class FtpInboundChannelAdapterParserTests {
public static class TestSessionFactoryBean implements FactoryBean<DefaultFtpSessionFactory> {
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public DefaultFtpSessionFactory getObject() throws Exception {
DefaultFtpSessionFactory factory = mock(DefaultFtpSessionFactory.class);
@@ -150,10 +151,12 @@ public class FtpInboundChannelAdapterParserTests {
return factory;
}
@Override
public Class<?> getObjectType() {
return DefaultFtpSessionFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}

View File

@@ -64,15 +64,15 @@ public class FtpOutboundChannelAdapterParserTests {
assertEquals(channel, TestUtils.getPropertyValue(consumer, "inputChannel"));
assertEquals("ftpOutbound", ((EventDrivenConsumer)consumer).getComponentName());
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler, "remoteFileSeparator");
String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler, "remoteFileTemplate.remoteFileSeparator");
assertNotNull(remoteFileSeparator);
assertEquals(".foo", TestUtils.getPropertyValue(handler, "temporaryFileSuffix", String.class));
assertEquals(".foo", TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryFileSuffix", String.class));
assertEquals("", remoteFileSeparator);
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "directoryExpressionProcessor"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectoryExpressionProcessor"));
Object sfProperty = TestUtils.getPropertyValue(handler, "sessionFactory");
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor"));
assertNotNull(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor"));
Object sfProperty = TestUtils.getPropertyValue(handler, "remoteFileTemplate.sessionFactory");
assertEquals(DefaultFtpSessionFactory.class, sfProperty.getClass());
DefaultFtpSessionFactory sessionFactory = (DefaultFtpSessionFactory) sfProperty;
assertEquals("localhost", TestUtils.getPropertyValue(sessionFactory, "host"));
@@ -99,7 +99,7 @@ public class FtpOutboundChannelAdapterParserTests {
ApplicationContext ac = new ClassPathXmlApplicationContext(
"FtpOutboundChannelAdapterParserTests-context.xml", this.getClass());
Object adapter = ac.getBean("simpleAdapter");
Object sfProperty = TestUtils.getPropertyValue(adapter, "handler.sessionFactory");
Object sfProperty = TestUtils.getPropertyValue(adapter, "handler.remoteFileTemplate.sessionFactory");
assertEquals(CachingSessionFactory.class, sfProperty.getClass());
Object innerSfProperty = TestUtils.getPropertyValue(sfProperty, "sessionFactory");
assertEquals(DefaultFtpSessionFactory.class, innerSfProperty.getClass());
@@ -121,7 +121,7 @@ public class FtpOutboundChannelAdapterParserTests {
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-context.xml", this.getClass());
FileTransferringMessageHandler<?> handler =
(FileTransferringMessageHandler<?>)TestUtils.getPropertyValue(ac.getBean("ftpOutbound3"), "handler");
assertFalse((Boolean)TestUtils.getPropertyValue(handler,"useTemporaryFileName"));
assertFalse((Boolean)TestUtils.getPropertyValue(handler,"remoteFileTemplate.useTemporaryFileName"));
}
@Test
@@ -131,16 +131,16 @@ public class FtpOutboundChannelAdapterParserTests {
Object consumer = ac.getBean("withBeanExpressions");
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
ExpressionEvaluatingMessageProcessor<?> dirExpProc = TestUtils.getPropertyValue(handler,
"directoryExpressionProcessor", ExpressionEvaluatingMessageProcessor.class);
"remoteFileTemplate.directoryExpressionProcessor", ExpressionEvaluatingMessageProcessor.class);
assertNotNull(dirExpProc);
Message<String> message = MessageBuilder.withPayload("qux").build();
assertEquals("foo", dirExpProc.processMessage(message));
ExpressionEvaluatingMessageProcessor<?> tempDirExpProc = TestUtils.getPropertyValue(handler,
"temporaryDirectoryExpressionProcessor", ExpressionEvaluatingMessageProcessor.class);
"remoteFileTemplate.temporaryDirectoryExpressionProcessor", ExpressionEvaluatingMessageProcessor.class);
assertNotNull(tempDirExpProc);
assertEquals("bar", tempDirExpProc.processMessage(message));
DefaultFileNameGenerator generator = TestUtils.getPropertyValue(handler,
"fileNameGenerator", DefaultFileNameGenerator.class);
"remoteFileTemplate.fileNameGenerator", DefaultFileNameGenerator.class);
assertNotNull(generator);
assertEquals("baz", generator.generateFileName(message));
}

View File

@@ -29,6 +29,7 @@
command-options="-1 -f"
expression="payload"
order="1"
mput-regex=".*"
/>
<bean id="fooString" class="java.lang.String">
@@ -49,6 +50,7 @@
order="2"
requires-reply="false"
local-filename-generator-expression="#remoteFileName.toUpperCase() + '.a' + @fooString"
mput-pattern="*"
>
<int-ftp:request-handler-advice-chain>
<bean class="org.springframework.integration.ftp.config.FtpOutboundGatewayParserTests$FooAdvice" />
@@ -66,6 +68,26 @@
order="1"
/>
<int-ftp:outbound-gateway id="gateway4"
session-factory="csf"
request-channel="inbound1"
reply-channel="outbound"
command="mput"
expression="payload"
remote-directory="/foo"
remote-file-separator="X"
auto-create-directory="true"
remote-filename-generator="fileNameGenerator"
temporary-remote-directory="/bar"
rename-expression="'foo'"
order="1"
mput-regex=".*"
/>
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
</bean>
<int-ftp:outbound-gateway id="withBeanExpression"
local-directory="local-test-dir"
session-factory="sf"

View File

@@ -18,28 +18,34 @@ package org.springframework.integration.ftp.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.expression.Expression;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Command;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Option;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.ftp.gateway.FtpOutboundGateway;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
@@ -65,17 +71,23 @@ public class FtpOutboundGatewayParserTests {
@Autowired
AbstractEndpoint gateway3;
@Autowired
AbstractEndpoint gateway4;
@Autowired
AbstractEndpoint withBeanExpression;
@Autowired
FileNameGenerator generator;
private static volatile int adviceCalled;
@Test
public void testGateway1() {
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway1,
"handler", FtpOutboundGateway.class);
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileTemplate.remoteFileSeparator"));
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
@@ -90,15 +102,16 @@ public class FtpOutboundGatewayParserTests {
Long sendTimeout = TestUtils.getPropertyValue(gateway, "messagingTemplate.sendTimeout", Long.class);
assertEquals(Long.valueOf(777), sendTimeout);
assertTrue(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class));
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(RegexPatternFileListFilter.class));
}
@Test
public void testGateway2() throws Exception {
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway2,
"handler", FtpOutboundGateway.class);
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
assertTrue(TestUtils.getPropertyValue(gateway, "sessionFactory") instanceof CachingSessionFactory);
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileTemplate.remoteFileSeparator"));
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
assertTrue(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory") instanceof CachingSessionFactory);
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
@@ -124,18 +137,37 @@ public class FtpOutboundGatewayParserTests {
}
});
assertEquals("FOO.afoo", genMethod.get().invoke(gateway, new GenericMessage<String>(""), "foo"));
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(SimplePatternFileListFilter.class));
}
@Test
public void testGatewayMv() {
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway3,
"handler", FtpOutboundGateway.class);
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
assertEquals(Command.MV, TestUtils.getPropertyValue(gateway, "command"));
assertEquals("'foo'", TestUtils.getPropertyValue(gateway, "renameProcessor.expression.expression"));
}
@Test
public void testGatewayMPut() {
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway4,
"handler", FtpOutboundGateway.class);
assertNotNull(TestUtils.getPropertyValue(gateway, "remoteFileTemplate.sessionFactory"));
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
assertEquals(Command.MPUT, TestUtils.getPropertyValue(gateway, "command"));
assertEquals("'foo'", TestUtils.getPropertyValue(gateway, "renameProcessor.expression.expression"));
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"), Matchers.instanceOf(RegexPatternFileListFilter.class));
assertSame(generator, TestUtils.getPropertyValue(gateway, "remoteFileTemplate.fileNameGenerator"));
assertEquals("/foo",
TestUtils.getPropertyValue(gateway, "remoteFileTemplate.directoryExpressionProcessor.expression", Expression.class)
.getExpressionString());
assertEquals("/bar",
TestUtils.getPropertyValue(gateway, "remoteFileTemplate.temporaryDirectoryExpressionProcessor.expression", Expression.class)
.getExpressionString());
}
@Test
public void testWithBeanExpression() {
FtpOutboundGateway gateway = TestUtils.getPropertyValue(withBeanExpression,

View File

@@ -44,9 +44,9 @@ public class FtpsOutboundChannelAdapterParserTests {
assertEquals(ac.getBean("ftpChannel"), TestUtils.getPropertyValue(consumer, "inputChannel"));
assertEquals("ftpOutbound", ((EventDrivenConsumer)consumer).getComponentName());
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
DefaultFtpsSessionFactory sf = TestUtils.getPropertyValue(handler, "sessionFactory", DefaultFtpsSessionFactory.class);
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset"));
DefaultFtpsSessionFactory sf = TestUtils.getPropertyValue(handler, "remoteFileTemplate.sessionFactory", DefaultFtpsSessionFactory.class);
assertEquals("localhost", TestUtils.getPropertyValue(sf, "host"));
assertEquals(22, TestUtils.getPropertyValue(sf, "port"));
}

View File

@@ -33,11 +33,14 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
@@ -46,8 +49,13 @@ import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.ftp.filters.FtpPersistentAcceptOnceFileListFilter;
import org.springframework.integration.ftp.filters.FtpRegexPatternFileListFilter;
import org.springframework.integration.ftp.session.AbstractFtpSessionFactory;
import org.springframework.integration.metadata.PropertiesPersistingMetadataStore;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
/**
@@ -61,6 +69,7 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
private static FTPClient ftpClient = mock(FTPClient.class);
@Before
@After
public void cleanup(){
File file = new File("test");
@@ -86,7 +95,16 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
synchronizer.setDeleteRemoteFiles(true);
synchronizer.setPreserveTimestamp(true);
synchronizer.setRemoteDirectory("remote-test-dir");
synchronizer.setFilter(new FtpRegexPatternFileListFilter(".*\\.test$"));
FtpRegexPatternFileListFilter patternFilter = new FtpRegexPatternFileListFilter(".*\\.test$");
PropertiesPersistingMetadataStore store = new PropertiesPersistingMetadataStore();
store.setBaseDirectory("test");
FtpPersistentAcceptOnceFileListFilter persistFilter =
new FtpPersistentAcceptOnceFileListFilter(store, "foo");
List<FileListFilter<FTPFile>> filters = new ArrayList<FileListFilter<FTPFile>>();
filters.add(persistFilter);
filters.add(patternFilter);
CompositeFileListFilter<FTPFile> filter = new CompositeFileListFilter<FTPFile>(filters);
synchronizer.setFilter(filter);
synchronizer.setIntegrationEvaluationContext(ExpressionUtils.createStandardEvaluationContext());
ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
@@ -120,28 +138,49 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
assertTrue(new File("test/A.TEST.a").exists());
assertTrue(new File("test/B.TEST.a").exists());
TestUtils.getPropertyValue(ms, "localFileListFilter.seen", Queue.class).clear();
new File("test/A.TEST.a").delete();
new File("test/B.TEST.a").delete();
// the remote filter should prevent a re-fetch
nothing = ms.receive();
assertNull(nothing);
}
public static class TestFtpSessionFactory extends AbstractFtpSessionFactory<FTPClient> {
private final Collection<Object> ftpFiles = new ArrayList<Object>();
private void init() {
String[] files = new File("remote-test-dir").list();
for (String fileName : files) {
FTPFile file = new FTPFile();
file.setName(fileName);
file.setType(FTPFile.FILE_TYPE);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 1);
file.setTimestamp(calendar);
ftpFiles.add(file);
}
}
@Override
protected FTPClient createClientInstance() {
if (this.ftpFiles.size() == 0) {
this.init();
}
try {
when(ftpClient.getReplyCode()).thenReturn(250);
when(ftpClient.login("kermit", "frog")).thenReturn(true);
when(ftpClient.changeWorkingDirectory(Mockito.anyString())).thenReturn(true);
String[] files = new File("remote-test-dir").list();
Collection<Object> ftpFiles = new ArrayList<Object>();
for (String fileName : files) {
FTPFile file = new FTPFile();
file.setName(fileName);
file.setType(FTPFile.FILE_TYPE);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 1);
file.setTimestamp(calendar);
ftpFiles.add(file);
when(ftpClient.retrieveFile(Mockito.eq("remote-test-dir/" + fileName) , Mockito.any(OutputStream.class))).thenReturn(true);
}
when(ftpClient.listFiles("remote-test-dir")).thenReturn(ftpFiles.toArray(new FTPFile[]{}));

View File

@@ -56,6 +56,7 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.FileInfo;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.ftp.session.AbstractFtpSessionFactory;
import org.springframework.messaging.support.GenericMessage;
@@ -94,6 +95,7 @@ public class FtpOutboundTests {
FileTransferringMessageHandler<FTPFile> handler = new FileTransferringMessageHandler<FTPFile>(sessionFactory);
handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir"));
handler.setFileNameGenerator(new FileNameGenerator() {
@Override
public String generateFileName(Message<?> message) {
return "handlerContent.test";
}
@@ -117,6 +119,7 @@ public class FtpOutboundTests {
FileTransferringMessageHandler<FTPFile> handler = new FileTransferringMessageHandler<FTPFile>(sessionFactory);
handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir"));
handler.setFileNameGenerator(new FileNameGenerator() {
@Override
public String generateFileName(Message<?> message) {
return "handlerContent.test";
}
@@ -138,6 +141,7 @@ public class FtpOutboundTests {
FileTransferringMessageHandler<FTPFile> handler = new FileTransferringMessageHandler<FTPFile>(sessionFactory);
handler.setRemoteDirectoryExpression(new LiteralExpression(targetDir.getName()));
handler.setFileNameGenerator(new FileNameGenerator() {
@Override
public String generateFileName(Message<?> message) {
return ((File)message.getPayload()).getName() + ".test";
}
@@ -163,6 +167,7 @@ public class FtpOutboundTests {
FileTransferringMessageHandler<FTPFile> handler = new FileTransferringMessageHandler<FTPFile>(sessionFactory);
handler.setRemoteDirectoryExpression(new LiteralExpression(targetDir.getName()));
handler.setFileNameGenerator(new FileNameGenerator() {
@Override
public String generateFileName(Message<?> message) {
return ((File)message.getPayload()).getName() + ".test";
}
@@ -172,7 +177,7 @@ public class FtpOutboundTests {
File srcFile = new File(UUID.randomUUID() + ".txt");
Log logger = spy(TestUtils.getPropertyValue(handler, "logger", Log.class));
Log logger = spy(TestUtils.getPropertyValue(handler, "remoteFileTemplate.logger", Log.class));
when(logger.isWarnEnabled()).thenReturn(true);
final AtomicReference<String> logged = new AtomicReference<String>();
doAnswer(new Answer<Object>(){
@@ -184,7 +189,8 @@ public class FtpOutboundTests {
return null;
}
}).when(logger).warn(Mockito.anyString());
new DirectFieldAccessor(handler).setPropertyValue("logger", logger);
RemoteFileTemplate<?> template = TestUtils.getPropertyValue(handler, "remoteFileTemplate", RemoteFileTemplate.class);
new DirectFieldAccessor(template).setPropertyValue("logger", logger);
handler.handleMessage(new GenericMessage<File>(srcFile));
assertNotNull(logged.get());
assertEquals("File " + srcFile.toString() + " does not exist", logged.get());
@@ -242,6 +248,7 @@ public class FtpOutboundTests {
when(ftpClient.changeWorkingDirectory(Mockito.anyString())).thenReturn(true);
when(ftpClient.printWorkingDirectory()).thenReturn("remote-target-dir");
when(ftpClient.storeFile(Mockito.anyString(), Mockito.any(InputStream.class))).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
String fileName = (String) invocation.getArguments()[0];
InputStream fis = (InputStream) invocation.getArguments()[1];
@@ -250,6 +257,7 @@ public class FtpOutboundTests {
}
});
when(ftpClient.rename(Mockito.anyString(), Mockito.anyString())).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation)
throws Throwable {
File file = new File((String) invocation.getArguments()[0]);

View File

@@ -68,4 +68,40 @@
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
<int:channel id="inboundMPut"/>
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMPut"
command="mput"
auto-create-directory="true"
filename-pattern="*.txt"
expression="payload"
remote-directory="ftpTarget"
reply-channel="output"/>
<int:channel id="inboundMPutRecursive"/>
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMPutRecursive"
command="mput"
command-options="-R"
auto-create-directory="true"
filename-pattern="*.txt"
expression="payload"
remote-directory="ftpTarget"
reply-channel="output"/>
<int:channel id="inboundMPutRecursiveFiltered"/>
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMPutRecursiveFiltered"
command="mput"
command-options="-R"
mput-regex="(.*1.txt|sub.*)"
auto-create-directory="true"
filename-pattern="*.txt"
expression="payload"
remote-directory="ftpTarget"
reply-channel="output"/>
</beans>

View File

@@ -16,24 +16,36 @@
package org.springframework.integration.ftp.outbound;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.apache.commons.net.ftp.FTPFile;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.file.remote.InputStreamCallback;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.ftp.TesFtpServer;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
@@ -44,6 +56,8 @@ import org.springframework.util.FileCopyUtils;
/**
* @author Artem Bilan
* @author Gary Russell
*
* @since 3.0
*/
@ContextConfiguration
@@ -51,7 +65,10 @@ import org.springframework.util.FileCopyUtils;
public class FtpServerOutboundTests {
@Autowired
public TesFtpServer ftpServer;
private TesFtpServer ftpServer;
@Autowired
private SessionFactory<FTPFile> ftpSessionFactory;
@Autowired
private PollableChannel output;
@@ -71,10 +88,19 @@ public class FtpServerOutboundTests {
@Autowired
private DirectChannel inboundMGetRecursiveFiltered;
@Autowired
private DirectChannel inboundMPut;
@Autowired
private DirectChannel inboundMPutRecursive;
@Autowired
private DirectChannel inboundMPutRecursiveFiltered;
@Before
public void setup() {
TesFtpServer.recursiveDelete(ftpServer.getTargetLocalDirectory());
TesFtpServer.recursiveDelete(ftpServer.getTargetFtpDirectory());
this.ftpServer.recursiveDelete(ftpServer.getTargetLocalDirectory());
this.ftpServer.recursiveDelete(ftpServer.getTargetFtpDirectory());
}
@Test
@@ -176,7 +202,7 @@ public class FtpServerOutboundTests {
@Test
public void testInt3100RawGET() throws Exception {
Session<?> session = this.ftpServer.ftpSessionFactory().getSession();
Session<?> session = this.ftpSessionFactory.getSession();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileCopyUtils.copy(session.readRaw("ftpSource/ftpSource1.txt"), baos);
assertTrue(session.finalizeRaw());
@@ -190,5 +216,90 @@ public class FtpServerOutboundTests {
session.close();
}
@Test
public void testRawGETWithTemplate() throws Exception {
RemoteFileTemplate<FTPFile> template = new RemoteFileTemplate<FTPFile>(this.ftpSessionFactory);
template.setFileNameExpression(new SpelExpressionParser().parseExpression("payload"));
template.setBeanFactory(mock(BeanFactory.class));
template.afterPropertiesSet();
final ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
assertTrue(template.get(new GenericMessage<String>("ftpSource/ftpSource1.txt"), new InputStreamCallback() {
@Override
public void doWithInputStream(InputStream stream) throws IOException {
FileCopyUtils.copy(stream, baos1);
}
}));
assertEquals("source1", new String(baos1.toByteArray()));
final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
assertTrue(template.get(new GenericMessage<String>("ftpSource/ftpSource2.txt"), new InputStreamCallback() {
@Override
public void doWithInputStream(InputStream stream) throws IOException {
FileCopyUtils.copy(stream, baos2);
}
}));
assertEquals("source2", new String(baos2.toByteArray()));
}
@Test
public void testInt3088MPutNotRecursive() {
this.inboundMPut.send(new GenericMessage<File>(this.ftpServer.getSourceLocalDirectory()));
@SuppressWarnings("unchecked")
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
assertNotNull(out);
assertEquals(2, out.getPayload().size());
assertThat(out.getPayload().get(0),
not(equalTo(out.getPayload().get(1))));
assertThat(
out.getPayload().get(0),
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt")));
assertThat(
out.getPayload().get(1),
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt")));
}
@Test
public void testInt3088MPutRecursive() {
this.inboundMPutRecursive.send(new GenericMessage<File>(this.ftpServer.getSourceLocalDirectory()));
@SuppressWarnings("unchecked")
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
assertNotNull(out);
assertEquals(3, out.getPayload().size());
assertThat(out.getPayload().get(0),
not(equalTo(out.getPayload().get(1))));
assertThat(
out.getPayload().get(0),
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
assertThat(
out.getPayload().get(1),
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
assertThat(
out.getPayload().get(2),
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
}
@Test
public void testInt3088MPutRecursiveFiltered() {
this.inboundMPutRecursiveFiltered.send(new GenericMessage<File>(this.ftpServer.getSourceLocalDirectory()));
@SuppressWarnings("unchecked")
Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
assertNotNull(out);
assertEquals(2, out.getPayload().size());
assertThat(out.getPayload().get(0),
not(equalTo(out.getPayload().get(1))));
assertThat(
out.getPayload().get(0),
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
assertThat(
out.getPayload().get(1),
anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt"),
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
}
}

View File

@@ -21,6 +21,7 @@ import groovy.lang.GString;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.lang.MissingPropertyException;
import groovy.lang.Script;
import java.util.Map;
@@ -29,6 +30,7 @@ import java.util.concurrent.locks.ReentrantLock;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.integration.scripting.AbstractScriptExecutingMessageProcessor;
import org.springframework.integration.scripting.ScriptVariableGenerator;
@@ -136,12 +138,9 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti
try {
GroovyObject goo = (GroovyObject) this.scriptClass.newInstance();
GroovyObjectCustomizer groovyObjectCustomizer = this.customizerDecorator;
if (variables != null) {
// Override empty Script.Binding with new one with 'variables'
groovyObjectCustomizer = new BindingOverwriteGroovyObjectCustomizerDecorator(new Binding(variables));
((VariableBindingGroovyObjectCustomizerDecorator) groovyObjectCustomizer).setCustomizer(this.customizerDecorator);
}
VariableBindingGroovyObjectCustomizerDecorator groovyObjectCustomizer =
new BindingOverwriteGroovyObjectCustomizerDecorator(new BeanFactoryFallbackBinding(variables));
groovyObjectCustomizer.setCustomizer(this.customizerDecorator);
if (goo instanceof Script) {
// Allow metaclass and other customization.
@@ -164,4 +163,34 @@ public class GroovyScriptExecutingMessageProcessor extends AbstractScriptExecuti
}
}
private class BeanFactoryFallbackBinding extends Binding {
private BeanFactoryFallbackBinding(Map<?, ?> variables) {
super(variables);
}
@Override
public Object getVariable(String name) {
try {
return super.getVariable(name);
}
catch (MissingPropertyException e) {
// Original {@link Binding} doesn't have 'variable' for the given 'name'.
// Try to resolve it as 'bean' from the given <code>beanFactory</code>.
}
if (GroovyScriptExecutingMessageProcessor.this.beanFactory == null) {
throw new MissingPropertyException(name, this.getClass());
}
try {
return GroovyScriptExecutingMessageProcessor.this.beanFactory.getBean(name);
}
catch (NoSuchBeanDefinitionException e) {
throw new MissingPropertyException(name, this.getClass(), e);
}
}
}
}

View File

@@ -10,30 +10,38 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<service-activator input-channel="referencedScriptInput">
<groovy:script location="org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy"
<groovy:script location="org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy"
customizer="groovyCustomizer">
<groovy:variable name="foo" value="foo"/>
<groovy:variable name="bar" value="bar"/>
<groovy:variable name="date" ref="date"/>
</groovy:script>
</service-activator>
<service-activator input-channel="withScriptVariableGenerator">
<groovy:script location="org/springframework/integration/groovy/config/GroovyServiceActivatorTests.groovy"
script-variable-generator="scriptVarSource" customizer="groovyCustomizer"/>
</service-activator>
<beans:bean id="groovyCustomizer"
<beans:bean id="groovyCustomizer"
class="org.springframework.integration.groovy.config.GroovyServiceActivatorTests.MyGroovyCustomizer"/>
<beans:bean id="scriptVarSource"
<beans:bean id="scriptVarSource"
class="org.springframework.integration.groovy.config.GroovyServiceActivatorTests.SampleScriptVariSource"/>
<service-activator input-channel="inlineScriptInput">
<groovy:script customizer="groovyCustomizer">
<groovy:script customizer="groovyCustomizer" variables="date-ref=date">
<![CDATA[
return "inline-$payload"
return "inline-$payload : ${date.format('dd.mm.yyyy')}"
]]>
</groovy:script>
</service-activator>
<service-activator input-channel="scriptWithoutVariablesInput">
<groovy:script>
<![CDATA[
return "withoutVariables-$payload : ${date.format('dd.mm.yyyy')}"
]]>
</groovy:script>
</service-activator>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:groovy="http://www.springframework.org/schema/integration/groovy"
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/groovy http://www.springframework.org/schema/integration/groovy/spring-integration-groovy.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<service-activator input-channel="inlineScriptInput">
<groovy:script>
<![CDATA[
return "inline-$payload-" + "$foo" + " - " + bar + " - " + date
]]>
<groovy:variable name="foo" value="foo"/>
<groovy:variable name="bar" value="bar"/>
</groovy:script>
</service-activator>
</beans:beans>

View File

@@ -22,7 +22,11 @@ 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 groovy.lang.GroovyObject;
import groovy.lang.MissingPropertyException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -34,22 +38,20 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.handler.ReplyRequiredException;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.scripting.ScriptVariableGenerator;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scripting.groovy.GroovyObjectCustomizer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import groovy.lang.GroovyObject;
import groovy.lang.MissingPropertyException;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
@@ -73,6 +75,9 @@ public class GroovyServiceActivatorTests {
@Autowired
private MessageChannel invalidInlineScript;
@Autowired
private MessageChannel scriptWithoutVariablesInput;
@Autowired
private MyGroovyCustomizer groovyCustomizer;
@@ -134,13 +139,38 @@ public class GroovyServiceActivatorTests {
Message<?> message = MessageBuilder.withPayload("test-" + i).setReplyChannel(replyChannel).build();
this.inlineScriptInput.send(message);
}
assertEquals("inline-test-1", replyChannel.receive(0).getPayload());
assertEquals("inline-test-2", replyChannel.receive(0).getPayload());
assertEquals("inline-test-3", replyChannel.receive(0).getPayload());
DateFormat format = new SimpleDateFormat("dd.mm.yyyy");
String now = format.format(new Date());
assertEquals("inline-test-1 : " + now, replyChannel.receive(0).getPayload());
assertEquals("inline-test-2 : " + now, replyChannel.receive(0).getPayload());
assertEquals("inline-test-3 : " + now, replyChannel.receive(0).getPayload());
assertNull(replyChannel.receive(0));
assertTrue(groovyCustomizer.executed);
}
@Test
public void testScriptWithoutVariables() throws Exception{
PollableChannel replyChannel = new QueueChannel();
for (int i = 1; i <= 3; i++) {
Message<?> message = MessageBuilder.withPayload("test-" + i).setReplyChannel(replyChannel).build();
this.scriptWithoutVariablesInput.send(message);
}
DateFormat format = new SimpleDateFormat("dd.mm.yyyy");
String now = format.format(new Date());
assertEquals("withoutVariables-test-1 : " + now, replyChannel.receive(0).getPayload());
assertEquals("withoutVariables-test-2 : " + now, replyChannel.receive(0).getPayload());
assertEquals("withoutVariables-test-3 : " + now, replyChannel.receive(0).getPayload());
assertNull(replyChannel.receive(0));
}
//INT-2399
@Test(expected = MessageHandlingException.class)
public void invalidInlineScript() throws Exception {
@@ -158,11 +188,6 @@ public class GroovyServiceActivatorTests {
}
@Test(expected=BeanDefinitionParsingException.class)
public void inlineScriptAndVariables() throws Exception{
new ClassPathXmlApplicationContext("GroovyServiceActivatorTests-fail-context.xml", this.getClass());
}
@Test(expected=BeanDefinitionParsingException.class)
public void variablesAndScriptVariableGenerator() throws Exception{
new ClassPathXmlApplicationContext("GroovyServiceActivatorTests-fail-withgenerator-context.xml", this.getClass());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ import org.springframework.util.xml.DomUtils;
* @author Oleg Zhurakousky
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @since 2.0.2
*/
abstract class HttpAdapterParsingUtils {
@@ -52,9 +53,23 @@ abstract class HttpAdapterParsingUtils {
}
}
static void configureUriVariableExpressions(BeanDefinitionBuilder builder, Element element) {
static void configureUriVariableExpressions(BeanDefinitionBuilder builder, ParserContext parserContext, Element element) {
String uriVariablesExpression = element.getAttribute("uri-variables-expression");
List<Element> uriVariableElements = DomUtils.getChildElementsByTagName(element, "uri-variable");
if (!CollectionUtils.isEmpty(uriVariableElements)) {
boolean hasUriVariableExpressions = !CollectionUtils.isEmpty(uriVariableElements);
if (StringUtils.hasText(uriVariablesExpression)) {
if (hasUriVariableExpressions) {
parserContext.getReaderContext().error("'uri-variables-expression' attribute " +
"and 'uri-variable' sub-elements are mutually exclusive.", element);
}
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
.addConstructorArgValue(uriVariablesExpression);
builder.addPropertyValue("uriVariablesExpression", beanDefinitionBuilder.getBeanDefinition());
}
if (hasUriVariableExpressions) {
ManagedMap<String, Object> uriVariableExpressions = new ManagedMap<String, Object>();
for (Element uriVariableElement : uriVariableElements) {
String name = uriVariableElement.getAttribute("name");

View File

@@ -77,7 +77,7 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "charset");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload");
HttpAdapterParsingUtils.setExpectedResponseOrExpression(element, parserContext, builder);
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, element);
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, parserContext, element);
return builder.getBeanDefinition();
}

View File

@@ -87,7 +87,7 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, element);
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, parserContext, element);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "transfer-cookies");
return builder;
}

View File

@@ -111,6 +111,10 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private volatile HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.outboundMapper();
private volatile Expression uriVariablesExpression;
/**
* Create a handler that will send requests to the provided URI.
*/
@@ -280,6 +284,15 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
}
/**
* Set the {@link Expression} to evaluate against the outbound message; the expression
* must evaluate to a Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
*/
public void setUriVariablesExpression(Expression uriVariablesExpression) {
this.uriVariablesExpression = uriVariablesExpression;
}
/**
* Set to true if you wish 'Set-Cookie' headers in responses to be
* transferred as 'Cookie' headers in subsequent interactions for
@@ -314,6 +327,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
private class ClassToStringConverter implements Converter<Class<?>, String> {
@Override
public String convert(Class<?> source) {
return source.getName();
}
@@ -326,6 +340,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
*
*/
private class ObjectToStringConverter implements Converter<Object, String> {
@Override
public String convert(Object source) {
if (source instanceof Class) {
return ((Class<?>) source).getName();
@@ -351,11 +366,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
Class<?> expectedResponseType = this.determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
Map<String, Object> uriVariables = ExpressionEvalMap
.from(this.uriVariableExpressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
Map<String, ?> uriVariables = this.determineUriVariables(requestMessage);
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).buildAndExpand(uriVariables);
URI realUri = this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
ResponseEntity<?> httpResponse = this.restTemplate.exchange(realUri, httpMethod, httpRequest, expectedResponseType);
@@ -554,7 +565,6 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
return HttpMethod.valueOf(strHttpMethod);
}
private Class<?> determineExpectedResponseType(Message<?> requestMessage) throws Exception{
Class<?> expectedResponseType = null;
String expectedResponseTypeName = null;
@@ -568,4 +578,22 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
}
@SuppressWarnings("unchecked")
private Map<String, ?> determineUriVariables(Message<?> requestMessage) {
Map<String, ?> expressions;
if (this.uriVariablesExpression != null) {
expressions = this.uriVariablesExpression.getValue(this.evaluationContext, requestMessage, Map.class);
}
else {
expressions = this.uriVariableExpressions;
}
return ExpressionEvalMap.from(expressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
}
}

View File

@@ -322,78 +322,14 @@
<xsd:annotation>
<xsd:documentation>
Specify an expression for URI variable placeholder within 'url'.
This element is mutually exclusive with 'uri-variables-expression' attribute.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
URL to which the requests should be sent. It may include {placeholders} for
evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="url-expression" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
SpEL Expression resolving to a URL to which the requests should be sent. The resolved
value may include {placeholders} for further evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the real URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed,
for example by using the "url-expression". Default is "true".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
The HTTP method to use when executing requests with this adapter Default is POST.
This attribute cannot be provided if http-method-expression has a value.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="httpMethodEnumeration xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="http-method-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The SpEL expression to determine HTTP method, use when executing requests with this adapter,
dynamically. This attribute cannot be provided if http-method has a value.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.RestTemplate" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify the charset name to use for converting String-typed payloads to bytes.
The default is 'UTF-8'
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
<xsd:attribute name="extract-payload" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
@@ -404,93 +340,6 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The expected type to which the response body should be converted.
Default is 'org.springframework.http.ResponseEntity'.
This attribute cannot be provided if expected-response-type-expression has a value
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converters" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
all of the default converters that would normally be present on the underlying RestTemplate.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="header-mapper" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
Specify a reference to org.springframework.integration.mapping.HeaderMapper
implementation bean. Only one of 'header-mapper' or 'mapped-request-headers' attributes
can be provided.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapped-request-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of names of MessageHeaders to be mapped into the HttpHeaders of the HTTP request.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Request headers.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.ClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ResponseErrorHandler to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.ResponseErrorHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the order for invocation when this adapter is connected as a subscriber to a SubscribableChannel.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
@@ -525,96 +374,6 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
URL to which the requests should be sent. It may include {placeholders} for
evaluation against uri-variables.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="url-expression" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
SpEL Expression resolving to a URL to which the requests should be sent. The resolved
value may include {placeholders} for further evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the real URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed,
for example by using the "url-expression". Default is "true".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
The HTTP method to use when executing requests with this adapter. Default is POST.
This attribute cannot be provided if http-method-expression has a value.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="httpMethodEnumeration xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="http-method-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The SpEL expression to determine HTTP method, use when executing requests with this gateway,
dynamically. This attribute cannot be provided if http-method has a value.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converters" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
all of the default converters that would normally be present on the underlying RestTemplate.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="header-mapper" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
Specifies a reference to org.springframework.integration.mapping.HeaderMapper
implementation bean. Only one of 'header-mapper' or 'mapped-request-headers'('mapped-response-headers')
attributes
can be provided.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.RestTemplate" />
</tool:annotation>
<xsd:documentation>
The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapped-request-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of names of MessageHeaders to be mapped into the HttpHeaders of the HTTP request.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Request headers.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapped-response-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -626,76 +385,13 @@
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="extract-request-payload" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specifies whether the outbound message's payload should be extracted
when preparing the request body. Otherwise the Message instance itself
will be serialized.
The default value is 'true'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The expected type to which the response body should be converted.
Default is 'org.springframework.http.ResponseEntity'.
This attribute cannot be provided if expected-response-type-expression has a value
Specifies whether the outbound message's payload should be extracted
when preparing the request body. Otherwise the Message instance itself
will be serialized.
The default value is 'true'.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify the charset name to use for converting String-typed payloads to bytes.
The default is 'UTF-8'
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.ClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ResponseErrorHandler to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.ResponseErrorHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the order for invocation when this gateway is connected as a subscriber to a SubscribableChannel.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="transfer-cookies" type="xsd:string" default="false">
@@ -730,6 +426,7 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -873,4 +570,168 @@
</xsd:attribute>
</xsd:complexType>
<xsd:attributeGroup name="httpOutboundCommonAttributes">
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
URL to which the requests should be sent. It may include {placeholders} for
evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="url-expression" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
SpEL Expression resolving to a URL to which the requests should be sent. The resolved
value may include {placeholders} for further evaluation against uri-variables.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the real URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed,
for example by using the "url-expression". Default is "true".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
The HTTP method to use when executing requests with this adapter Default is POST.
This attribute cannot be provided if http-method-expression has a value.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="httpMethodEnumeration xsd:string" />
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="http-method-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The SpEL expression to determine HTTP method, use when executing requests with this adapter,
dynamically. This attribute cannot be provided if http-method has a value.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.RestTemplate" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify the charset name to use for converting String-typed payloads to bytes.
The default is 'UTF-8'
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The expected type to which the response body should be converted.
Default is 'org.springframework.http.ResponseEntity'.
This attribute cannot be provided if expected-response-type-expression has a value
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converters" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Provide a reference to a list of HttpMessageConverter instances. If specified, these converters will replace
all of the default converters that would normally be present on the underlying RestTemplate.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="header-mapper" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
Specify a reference to org.springframework.integration.mapping.HeaderMapper
implementation bean. Only one of 'header-mapper' or 'mapped-request-headers' attributes
can be provided.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapped-request-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of names of MessageHeaders to be mapped into the HttpHeaders of the HTTP request.
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Request headers.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.ClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ResponseErrorHandler to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.ResponseErrorHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the order for invocation when this adapter is connected as a subscriber to a SubscribableChannel.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="uri-variables-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies the SpEL expression to be evaluate as a Map for URI variable placeholders within 'url'.
This attribute is mutually exclusive with 'uri-variable' sub-elements.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
</xsd:schema>

View File

@@ -13,9 +13,9 @@
<si:channel id="requests"/>
<outbound-channel-adapter id="minimalConfig" url="http://localhost/test1" channel="requests"/>
<outbound-channel-adapter id="restTemplateConfig" url="http://localhost/test1" channel="requests" rest-template="customRestTemplate"/>
<beans:bean id="customRestTemplate" class="org.springframework.web.client.RestTemplate"/>
<outbound-channel-adapter id="fullConfig"
@@ -34,8 +34,14 @@
<uri-variable name="foo" expression="headers.bar"/>
</outbound-channel-adapter>
<util:map id="uriVariables">
<beans:entry key="foo1" value="bar1"/>
<beans:entry key="foo2" value="bar2"/>
</util:map>
<outbound-channel-adapter id="withUrlAndTemplate"
url="http://localhost/test1" channel="requests"
uri-variables-expression="@uriVariables"
rest-template="customRestTemplate"/>
<outbound-channel-adapter id="withUrlExpression" url-expression="'http://localhost/test1'" channel="requests"/>

View File

@@ -57,6 +57,7 @@ import org.springframework.web.client.RestTemplate;
* @author Mark Fisher
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@@ -166,6 +167,7 @@ public class HttpOutboundChannelAdapterParserTests {
}
@Test
@SuppressWarnings("uchecked")
public void withUrlAndTemplate() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.withUrlAndTemplate);
RestTemplate restTemplate =
@@ -185,6 +187,14 @@ public class HttpOutboundChannelAdapterParserTests {
assertEquals(HttpMethod.POST.name(), TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString());
assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
//INT-3055
Object uriVariablesExpression = handlerAccessor.getPropertyValue("uriVariablesExpression");
assertNotNull(uriVariablesExpression);
assertEquals("@uriVariables", ((Expression) uriVariablesExpression).getExpressionString());
Object uriVariableExpressions = handlerAccessor.getPropertyValue("uriVariableExpressions");
assertNotNull(uriVariableExpressions);
assertTrue(((Map<?, ?>) uriVariableExpressions).isEmpty());
}
@Test

View File

@@ -40,7 +40,13 @@
<uri-variable name="foo" expression="headers.bar"/>
</outbound-gateway>
<outbound-gateway id="withUrlExpression" url-expression="'http://localhost/test1'" request-channel="requests"/>
<util:map id="uriVariables">
<beans:entry key="foo1" value="bar1"/>
<beans:entry key="foo2" value="bar2"/>
</util:map>
<outbound-gateway id="withUrlExpression" url-expression="'http://localhost/test1'" request-channel="requests"
uri-variables-expression="@uriVariables"/>
<outbound-gateway id="withAdvice" url-expression="'http://localhost/test1'" request-channel="requests">
<request-handler-advice-chain>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -170,6 +170,14 @@ public class HttpOutboundGatewayParserTests {
assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
assertEquals(false, handlerAccessor.getPropertyValue("transferCookies"));
//INT-3055
Object uriVariablesExpression = handlerAccessor.getPropertyValue("uriVariablesExpression");
assertNotNull(uriVariablesExpression);
assertEquals("@uriVariables", ((Expression) uriVariablesExpression).getExpressionString());
Object uriVariableExpressions = handlerAccessor.getPropertyValue("uriVariableExpressions");
assertNotNull(uriVariableExpressions);
assertTrue(((Map<?, ?>) uriVariableExpressions).isEmpty());
}
@Test

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.http.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import java.io.IOException;
@@ -34,6 +35,8 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.integration.expression.ExpressionEvalMap;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
@@ -62,14 +65,13 @@ public class UriVariableExpressionTests {
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
Message<?> message = new GenericMessage<Object>("bar");
Exception exception = null;
try {
handler.handleMessage(message);
fail("Exception expected.");
}
catch (Exception e) {
exception = e;
assertEquals("intentional", e.getCause().getMessage());
}
assertEquals("intentional", exception.getCause().getMessage());
assertEquals("http://test/bar", uriHolder.get().toString());
}
@@ -92,15 +94,45 @@ public class UriVariableExpressionTests {
});
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
Message<?> message = new GenericMessage<Object>("bar");
Exception exception = null;
try {
handler.handleMessage(message);
handler.handleMessage(new GenericMessage<Object>("bar"));
fail("Exception expected.");
}
catch (Exception e) {
exception = e;
assertEquals("intentional", e.getCause().getMessage());
}
assertEquals("intentional", exception.getCause().getMessage());
assertEquals("http://test/bar", uriHolder.get().toString());
}
@Test
public void testInt3055UriVariablesExpression() throws Exception {
final AtomicReference<URI> uriHolder = new AtomicReference<URI>();
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler("http://test/{foo}");
handler.setRequestFactory(new SimpleClientHttpRequestFactory() {
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
uriHolder.set(uri);
throw new RuntimeException("intentional");
}
});
handler.setBeanFactory(mock(BeanFactory.class));
handler.setUriVariablesExpression(new SpelExpressionParser().parseExpression("headers.uriVariables"));
handler.afterPropertiesSet();
Map<String, String> expressions = new HashMap<String, String>();
expressions.put("foo", "bar");
Map<String, ?> expressionsMap = ExpressionEvalMap.from(expressions).usingSimpleCallback().build();
try {
handler.handleMessage(MessageBuilder.withPayload("test").setHeader("uriVariables", expressionsMap).build());
fail("Exception expected.");
}
catch (Exception e) {
assertEquals("intentional", e.getCause().getMessage());
}
assertEquals("http://test/bar", uriHolder.get().toString());
}

View File

@@ -24,26 +24,58 @@ package org.springframework.integration.ip;
* @author Dave Syer
* @since 2.0
*/
public abstract class IpHeaders {
public final class IpHeaders {
private static final String IP = "ip_";
private static final String TCP = IP + "tcp_";
/**
* The host name from which a TCP message or UDP packet was received. If
* {@code lookupHost} is {@code false}, this will contain the ip address.
*/
public static final String HOSTNAME = IP + "hostname";
/**
* The ip address from which a TCP message or UDP packet was received.
*/
public static final String IP_ADDRESS = IP + "address";
/**
* The remote port for a UDP packet.
*/
public static final String PORT = IP + "port";
/**
* The remote ip address to which UDP application-level acks will be sent. The
* framework includes acknowledgment information in the data packet.
*/
public static final String ACK_ADDRESS = IP + "ackTo";
/**
* A correlation id for UDP application-level acks. The
* framework includes acknowledgment information in the data packet.
*/
public static final String ACK_ID = IP + "ackId";
/**
* The remote port from which a TCP message was received.
*/
public static final String REMOTE_PORT = TCP + "remotePort";
/**
* A unique identifier for a TCP connection; set by the framework for
* inbound messages; when sending to a server-side inbound
* channel adapter, or replying to an inbound gateway, this header is
* required so the endpoint can determine which connection to send
* the message to.
*/
public static final String CONNECTION_ID = IP + "connectionId";
/**
* For information only - when using a cached or failover client connection
* factory, contains the actual underlying connection id.
*/
public static final String ACTUAL_CONNECTION_ID = IP + "actualConnectionId";
private IpHeaders() {}

View File

@@ -125,6 +125,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
this.port = port;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@@ -413,6 +414,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
*/
public abstract void close();
@Override
public void start() {
if (logger.isInfoEnabled()) {
logger.info("started " + this);
@@ -438,6 +440,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
/**
* Stops the server.
*/
@Override
public void stop() {
this.active = false;
this.close();
@@ -547,7 +550,6 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
else {
if (logger.isWarnEnabled()) {
logger.warn("Timing out TcpNioConnection " +
this.port + " : " +
connection.getConnectionId());
}
connection.publishConnectionExceptionEvent(new SocketTimeoutException("Timing out connection"));
@@ -581,16 +583,19 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
connection = (TcpNioConnection) key.attachment();
connection.setLastRead(System.currentTimeMillis());
this.taskExecutor.execute(new Runnable() {
@Override
public void run() {
try {
connection.readPacket();
} catch (Exception e) {
}
catch (Exception e) {
if (connection.isOpen()) {
logger.error("Exception on read " +
connection.getConnectionId() + " " +
e.getMessage());
connection.close();
} else {
}
else {
logger.debug("Connection closed");
}
}
@@ -633,6 +638,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
throw new UnsupportedOperationException("Nio server factory must override this method");
}
@Override
public int getPhase() {
return 0;
}
@@ -641,10 +647,12 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
* We are controlled by the startup options of
* the bound endpoint.
*/
@Override
public boolean isAutoStartup() {
return false;
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
@@ -688,6 +696,7 @@ public abstract class AbstractConnectionFactory extends IntegrationObjectSupport
this.removeClosedConnectionsAndReturnOpenConnectionIds();
}
@Override
public boolean isRunning() {
return this.active;
}

View File

@@ -16,6 +16,7 @@
package org.springframework.integration.ip.tcp.connection;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
import org.springframework.messaging.Message;
@@ -38,15 +39,25 @@ public abstract class TcpConnectionInterceptorSupport extends TcpConnectionSuppo
private Boolean realSender;
public TcpConnectionInterceptorSupport() {
super();
}
public TcpConnectionInterceptorSupport(ApplicationEventPublisher applicationEventPublisher) {
super(applicationEventPublisher);
}
@Override
public void close() {
this.theConnection.close();
}
@Override
public boolean isOpen() {
return this.theConnection.isOpen();
}
@Override
public Object getPayload() throws Exception {
return this.theConnection.getPayload();
}
@@ -61,10 +72,12 @@ public abstract class TcpConnectionInterceptorSupport extends TcpConnectionSuppo
return this.theConnection.getHostAddress();
}
@Override
public int getPort() {
return this.theConnection.getPort();
}
@Override
public Object getDeserializerStateKey() {
return this.theConnection.getDeserializerStateKey();
}
@@ -91,6 +104,7 @@ public abstract class TcpConnectionInterceptorSupport extends TcpConnectionSuppo
return this.theConnection.isSingleUse();
}
@Override
public void run() {
this.theConnection.run();
}
@@ -130,6 +144,7 @@ public abstract class TcpConnectionInterceptorSupport extends TcpConnectionSuppo
return this.theConnection.isServer();
}
@Override
public boolean onMessage(Message<?> message) {
if (this.tcpListener == null) {
if (message instanceof ErrorMessage) {
@@ -142,6 +157,7 @@ public abstract class TcpConnectionInterceptorSupport extends TcpConnectionSuppo
return this.tcpListener.onMessage(message);
}
@Override
public void send(Message<?> message) throws Exception {
this.theConnection.send(message);
}
@@ -170,12 +186,14 @@ public abstract class TcpConnectionInterceptorSupport extends TcpConnectionSuppo
return tcpListener;
}
@Override
public void addNewConnection(TcpConnection connection) {
if (this.tcpSender != null) {
this.tcpSender.addNewConnection(this);
}
}
@Override
public void removeDeadConnection(TcpConnection connection) {
if (this.tcpSender != null) {
this.tcpSender.removeDeadConnection(this);

View File

@@ -89,8 +89,12 @@ public abstract class TcpConnectionSupport implements TcpConnection {
private volatile boolean noReadErrorOnClose;
public TcpConnectionSupport() {
this(null);
}
public TcpConnectionSupport(ApplicationEventPublisher applicationEventPublisher) {
this.server = false;
this.applicationEventPublisher = null;
this.applicationEventPublisher = applicationEventPublisher;
}
/**
@@ -114,15 +118,18 @@ public abstract class TcpConnectionSupport implements TcpConnection {
this.hostAddress = inetAddress.getHostAddress();
if (lookupHost) {
this.hostName = inetAddress.getHostName();
} else {
}
else {
this.hostName = this.hostAddress;
}
}
int port = socket.getPort();
this.connectionId = this.hostName + ":" + port + ":" + UUID.randomUUID().toString();
int localPort = socket.getLocalPort();
this.connectionId = this.hostName + ":" + port + ":" + localPort + ":" + UUID.randomUUID().toString();
try {
this.soLinger = socket.getSoLinger();
} catch (SocketException e) { }
}
catch (SocketException e) { }
this.applicationEventPublisher = applicationEventPublisher;
if (connectionFactoryName != null) {
this.connectionFactoryName = connectionFactoryName;
@@ -150,6 +157,7 @@ public abstract class TcpConnectionSupport implements TcpConnection {
/**
* Closes this connection.
*/
@Override
public void close() {
if (this.sender != null) {
this.sender.removeDeadConnection(this);
@@ -203,6 +211,7 @@ public abstract class TcpConnectionSupport implements TcpConnection {
*
* @return the deserializer
*/
@Override
public Deserializer<?> getDeserializer() {
return this.deserializer;
}
@@ -218,6 +227,7 @@ public abstract class TcpConnectionSupport implements TcpConnection {
*
* @return the serializer
*/
@Override
public Serializer<?> getSerializer() {
return this.serializer;
}
@@ -266,6 +276,7 @@ public abstract class TcpConnectionSupport implements TcpConnection {
/**
* @return the listener
*/
@Override
public TcpListener getListener() {
return this.listener;
}
@@ -289,26 +300,32 @@ public abstract class TcpConnectionSupport implements TcpConnection {
*
* @return True if connection is used once.
*/
@Override
public boolean isSingleUse() {
return this.singleUse;
}
@Override
public boolean isServer() {
return server;
}
@Override
public long incrementAndGetConnectionSequence() {
return this.sequence.incrementAndGet();
}
@Override
public String getHostAddress() {
return this.hostAddress;
}
@Override
public String getHostName() {
return this.hostName;
}
@Override
public String getConnectionId() {
return this.connectionId;
}

View File

@@ -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.ip.tcp;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.integration.ip.tcp.connection.AbstractConnectionFactory;
import org.springframework.integration.ip.tcp.connection.HelloWorldInterceptorFactory;
/**
* @author Gary Russell
* @since 3.0
*
*/
public class AbstractTcpChannelAdapterTests {
private static final ApplicationEventPublisher NOOP_PUBLISHER = new ApplicationEventPublisher() {
@Override
public void publishEvent(ApplicationEvent event) {
}
};
protected HelloWorldInterceptorFactory newInterceptorFactory() {
HelloWorldInterceptorFactory factory = new HelloWorldInterceptorFactory();
factory.setApplicationEventPublisher(NOOP_PUBLISHER);
return factory;
}
protected void noopPublisher(AbstractConnectionFactory connectionFactory) {
connectionFactory.setApplicationEventPublisher(NOOP_PUBLISHER);
}
}

View File

@@ -40,6 +40,7 @@ import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import org.junit.Test;
import org.springframework.core.serializer.DefaultDeserializer;
import org.springframework.core.serializer.DefaultSerializer;
import org.springframework.messaging.Message;
@@ -49,7 +50,6 @@ import org.springframework.messaging.SubscribableChannel;
import org.springframework.integration.handler.ServiceActivatingHandler;
import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;
import org.springframework.integration.ip.tcp.connection.AbstractServerConnectionFactory;
import org.springframework.integration.ip.tcp.connection.HelloWorldInterceptorFactory;
import org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorFactory;
import org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorFactoryChain;
import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory;
@@ -63,12 +63,13 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @author Gary Russell
*/
public class TcpReceivingChannelAdapterTests {
public class TcpReceivingChannelAdapterTests extends AbstractTcpChannelAdapterTests {
@Test
public void testNet() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNetServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -88,6 +89,7 @@ public class TcpReceivingChannelAdapterTests {
message = channel.receive(10000);
assertNotNull(message);
assertEquals("Test2", new String((byte[]) message.getPayload()));
scf.stop();
}
@Test
@@ -97,6 +99,7 @@ public class TcpReceivingChannelAdapterTests {
final CountDownLatch latch2 = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port, 10);
@@ -115,6 +118,7 @@ public class TcpReceivingChannelAdapterTests {
}
});
AbstractClientConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -142,12 +146,14 @@ public class TcpReceivingChannelAdapterTests {
adapter.start();
adapter.stop();
latch2.countDown();
ccf.stop();
}
@Test
public void testNio() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
TcpNioServerConnectionFactory scf = new TcpNioServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -171,12 +177,14 @@ public class TcpReceivingChannelAdapterTests {
for (int i = 0; i < 1000; i++) {
assertTrue(results.remove("Test" + i));
}
scf.stop();
}
@Test
public void testNetShared() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNetServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -203,12 +211,14 @@ public class TcpReceivingChannelAdapterTests {
assertEquals("Test\r\n", new String(b));
readFully(socket.getInputStream(), b);
assertEquals("Test\r\n", new String(b));
scf.stop();
}
@Test
public void testNioShared() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
TcpNioServerConnectionFactory scf = new TcpNioServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -235,12 +245,14 @@ public class TcpReceivingChannelAdapterTests {
assertEquals("Test\r\n", new String(b));
readFully(socket.getInputStream(), b);
assertEquals("Test\r\n", new String(b));
scf.stop();
}
@Test
public void testNetSingleNoOutbound() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNetServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -265,12 +277,14 @@ public class TcpReceivingChannelAdapterTests {
results.add(new String((byte[]) message.getPayload()));
assertTrue(results.contains("Test1"));
assertTrue(results.contains("Test2"));
scf.stop();
}
@Test
public void testNioSingleNoOutbound() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
TcpNioServerConnectionFactory scf = new TcpNioServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -295,6 +309,7 @@ public class TcpReceivingChannelAdapterTests {
results.add(new String((byte[]) message.getPayload()));
assertTrue(results.contains("Test1"));
assertTrue(results.contains("Test2"));
scf.stop();
}
/**
@@ -311,6 +326,7 @@ public class TcpReceivingChannelAdapterTests {
public void testNetSingleShared() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNetServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -340,12 +356,14 @@ public class TcpReceivingChannelAdapterTests {
assertEquals("Test1\r\n", new String(b));
readFully(socket2.getInputStream(), b);
assertEquals("Test2\r\n", new String(b));
scf.stop();
}
@Test
public void testNioSingleShared() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
TcpNioServerConnectionFactory scf = new TcpNioServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -375,12 +393,14 @@ public class TcpReceivingChannelAdapterTests {
assertEquals("Test1\r\n", new String(b));
readFully(socket2.getInputStream(), b);
assertEquals("Test2\r\n", new String(b));
scf.stop();
}
@Test
public void testNioSingleSharedMany() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
TcpNioServerConnectionFactory scf = new TcpNioServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -413,48 +433,61 @@ public class TcpReceivingChannelAdapterTests {
readFully(sockets.remove(0).getInputStream(), b);
assertEquals("Test" + i + "\r\n", new String(b));
}
scf.stop();
}
@Test
public void testNetInterceptors() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNetServerConnectionFactory(port);
noopPublisher(scf);
interceptorsGuts(port, scf);
scf.stop();
}
@Test
public void testNetSingleNoOutboundInterceptors() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNetServerConnectionFactory(port);
noopPublisher(scf);
singleNoOutboundInterceptorsGuts(port, scf);
scf.stop();
}
@Test
public void testNetSingleSharedInterceptors() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNetServerConnectionFactory(port);
noopPublisher(scf);
singleSharedInterceptorsGuts(port, scf);
scf.stop();
}
@Test
public void testNioInterceptors() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNioServerConnectionFactory(port);
noopPublisher(scf);
interceptorsGuts(port, scf);
scf.stop();
}
@Test
public void testNioSingleNoOutboundInterceptors() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNioServerConnectionFactory(port);
noopPublisher(scf);
singleNoOutboundInterceptorsGuts(port, scf);
scf.stop();
}
@Test
public void testNioSingleSharedInterceptors() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNioServerConnectionFactory(port);
noopPublisher(scf);
singleSharedInterceptorsGuts(port, scf);
scf.stop();
}
private void interceptorsGuts(final int port, AbstractServerConnectionFactory scf) throws Exception {
@@ -464,9 +497,10 @@ public class TcpReceivingChannelAdapterTests {
TcpReceivingChannelAdapter adapter = new TcpReceivingChannelAdapter();
adapter.setConnectionFactory(scf);
TcpConnectionInterceptorFactoryChain fc = new TcpConnectionInterceptorFactoryChain();
fc.setInterceptors(new TcpConnectionInterceptorFactory[]
{new HelloWorldInterceptorFactory(),
new HelloWorldInterceptorFactory()});
fc.setInterceptors(new TcpConnectionInterceptorFactory[] {
newInterceptorFactory(),
newInterceptorFactory()
});
scf.setInterceptorFactoryChain(fc);
scf.setSoTimeout(10000);
scf.start();
@@ -498,9 +532,10 @@ public class TcpReceivingChannelAdapterTests {
scf.setSingleUse(true);
scf.setSoTimeout(10000);
TcpConnectionInterceptorFactoryChain fc = new TcpConnectionInterceptorFactoryChain();
fc.setInterceptors(new TcpConnectionInterceptorFactory[]
{new HelloWorldInterceptorFactory(),
new HelloWorldInterceptorFactory()});
fc.setInterceptors(new TcpConnectionInterceptorFactory[] {
newInterceptorFactory(),
newInterceptorFactory()
});
scf.setInterceptorFactoryChain(fc);
TcpReceivingChannelAdapter adapter = new TcpReceivingChannelAdapter();
adapter.setConnectionFactory(scf);
@@ -540,9 +575,10 @@ public class TcpReceivingChannelAdapterTests {
scf.setSingleUse(true);
scf.setSoTimeout(60000);
TcpConnectionInterceptorFactoryChain fc = new TcpConnectionInterceptorFactoryChain();
fc.setInterceptors(new TcpConnectionInterceptorFactory[]
{new HelloWorldInterceptorFactory(),
new HelloWorldInterceptorFactory()});
fc.setInterceptors(new TcpConnectionInterceptorFactory[] {
newInterceptorFactory(),
newInterceptorFactory()
});
scf.setInterceptorFactoryChain(fc);
TcpSendingMessageHandler handler = new TcpSendingMessageHandler();
handler.setConnectionFactory(scf);
@@ -583,6 +619,7 @@ public class TcpReceivingChannelAdapterTests {
public void testException() throws Exception {
final int port = SocketUtils.findAvailableServerSocket();
AbstractServerConnectionFactory scf = new TcpNetServerConnectionFactory(port);
noopPublisher(scf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
scf.setSerializer(serializer);
scf.setDeserializer(serializer);
@@ -605,6 +642,7 @@ public class TcpReceivingChannelAdapterTests {
message = errorChannel.receive(10000);
assertNotNull(message);
assertEquals("Failed", ((Exception) message.getPayload()).getCause().getMessage());
scf.stop();
}
private class FailingService {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -48,7 +49,8 @@ import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.serializer.DefaultDeserializer;
import org.springframework.core.serializer.DefaultSerializer;
@@ -60,7 +62,6 @@ import org.springframework.messaging.PollableChannel;
import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;
import org.springframework.integration.ip.tcp.connection.AbstractConnectionFactory;
import org.springframework.integration.ip.tcp.connection.AbstractServerConnectionFactory;
import org.springframework.integration.ip.tcp.connection.HelloWorldInterceptorFactory;
import org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorFactory;
import org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorFactoryChain;
import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory;
@@ -80,7 +81,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
* @author Artem Bilan
* @since 2.0
*/
public class TcpSendingMessageHandlerTests {
public class TcpSendingMessageHandlerTests extends AbstractTcpChannelAdapterTests {
private static final Log logger = LogFactory.getLog(TcpSendingMessageHandlerTests.class);
@@ -97,6 +98,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -118,6 +120,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -148,6 +151,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -169,6 +173,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -201,6 +206,7 @@ public class TcpSendingMessageHandlerTests {
handler.stop();
handler.start();
handler.stop();
adapter.stop();
}
@Test
@@ -209,6 +215,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -229,6 +236,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -262,6 +270,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -282,6 +291,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayStxEtxSerializer serializer = new ByteArrayStxEtxSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -312,6 +322,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -332,6 +343,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayStxEtxSerializer serializer = new ByteArrayStxEtxSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -365,6 +377,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -388,6 +401,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayLengthHeaderSerializer serializer = new ByteArrayLengthHeaderSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -418,6 +432,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -441,6 +456,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayLengthHeaderSerializer serializer = new ByteArrayLengthHeaderSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -474,6 +490,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -494,6 +511,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ccf.setSerializer(new DefaultSerializer());
ccf.setDeserializer(new DefaultDeserializer());
ccf.setSoTimeout(10000);
@@ -523,6 +541,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -543,6 +562,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ccf.setSerializer(new DefaultSerializer());
ccf.setDeserializer(new DefaultDeserializer());
ccf.setSoTimeout(10000);
@@ -576,6 +596,7 @@ public class TcpSendingMessageHandlerTests {
final Semaphore semaphore = new Semaphore(0);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -597,6 +618,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -620,6 +642,7 @@ public class TcpSendingMessageHandlerTests {
final Semaphore semaphore = new Semaphore(0);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -641,6 +664,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -664,6 +688,7 @@ public class TcpSendingMessageHandlerTests {
final Semaphore semaphore = new Semaphore(0);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -686,6 +711,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -721,6 +747,7 @@ public class TcpSendingMessageHandlerTests {
final Semaphore semaphore = new Semaphore(0);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -743,6 +770,7 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
@@ -778,20 +806,39 @@ public class TcpSendingMessageHandlerTests {
final Semaphore semaphore = new Semaphore(0);
final AtomicBoolean done = new AtomicBoolean();
final List<Socket> serverSockets = new ArrayList<Socket>();
Executors.newSingleThreadExecutor().execute(new Runnable() {
final ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port, 100);
latch.countDown();
for (int i = 0; i < 100; i++) {
Socket socket = server.accept();
final Socket socket = server.accept();
serverSockets.add(socket);
semaphore.release();
byte[] b = new byte[9];
readFully(socket.getInputStream(), b);
b = ("Reply" + i + "\r\n").getBytes();
socket.getOutputStream().write(b);
socket.close();
final int j = i;
exec.execute(new Runnable() {
@Override
public void run() {
semaphore.release();
byte[] b = new byte[9];
try {
readFully(socket.getInputStream(), b);
b = ("Reply" + j + "\r\n").getBytes();
socket.getOutputStream().write(b);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
try {
socket.close();
}
catch (IOException e) { }
}
}
});
}
server.close();
} catch (Exception e) {
@@ -802,12 +849,13 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ByteArrayCrLfSerializer serializer = new ByteArrayCrLfSerializer();
ccf.setSerializer(serializer);
ccf.setDeserializer(serializer);
ccf.setSoTimeout(10000);
ccf.setSingleUse(true);
ccf.setTaskExecutor(Executors.newFixedThreadPool(100));
ccf.setTaskExecutor(Executors.newCachedThreadPool());
ccf.start();
TcpSendingMessageHandler handler = new TcpSendingMessageHandler();
handler.setConnectionFactory(ccf);
@@ -845,6 +893,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -879,13 +928,15 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ccf.setSerializer(new DefaultSerializer());
ccf.setDeserializer(new DefaultDeserializer());
ccf.setSoTimeout(10000);
TcpConnectionInterceptorFactoryChain fc = new TcpConnectionInterceptorFactoryChain();
fc.setInterceptors(new TcpConnectionInterceptorFactory[]
{new HelloWorldInterceptorFactory(),
new HelloWorldInterceptorFactory()});
fc.setInterceptors(new TcpConnectionInterceptorFactory[] {
newInterceptorFactory(),
newInterceptorFactory()
});
ccf.setInterceptorFactoryChain(fc);
ccf.start();
TcpSendingMessageHandler handler = new TcpSendingMessageHandler();
@@ -913,6 +964,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -942,11 +994,12 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ccf.setSerializer(new DefaultSerializer());
ccf.setDeserializer(new DefaultDeserializer());
ccf.setSoTimeout(10000);
TcpConnectionInterceptorFactoryChain fc = new TcpConnectionInterceptorFactoryChain();
fc.setInterceptors(new TcpConnectionInterceptorFactory[] {new HelloWorldInterceptorFactory()});
fc.setInterceptors(new TcpConnectionInterceptorFactory[] {newInterceptorFactory()});
ccf.setInterceptorFactoryChain(fc);
ccf.start();
TcpSendingMessageHandler handler = new TcpSendingMessageHandler();
@@ -979,6 +1032,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
ServerSocket server = ServerSocketFactory.getDefault().createServerSocket(port);
@@ -1013,13 +1067,15 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNetClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ccf.setSerializer(new DefaultSerializer());
ccf.setDeserializer(new DefaultDeserializer());
ccf.setSoTimeout(10000);
TcpConnectionInterceptorFactoryChain fc = new TcpConnectionInterceptorFactoryChain();
fc.setInterceptors(new TcpConnectionInterceptorFactory[]
{new HelloWorldInterceptorFactory(),
new HelloWorldInterceptorFactory()});
fc.setInterceptors(new TcpConnectionInterceptorFactory[] {
newInterceptorFactory(),
newInterceptorFactory()
});
ccf.setInterceptorFactoryChain(fc);
ccf.setSingleUse(true);
ccf.start();
@@ -1037,6 +1093,7 @@ public class TcpSendingMessageHandlerTests {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean done = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
int i = 0;
try {
@@ -1070,13 +1127,15 @@ public class TcpSendingMessageHandlerTests {
}
});
AbstractConnectionFactory ccf = new TcpNioClientConnectionFactory("localhost", port);
noopPublisher(ccf);
ccf.setSerializer(new DefaultSerializer());
ccf.setDeserializer(new DefaultDeserializer());
ccf.setSoTimeout(10000);
TcpConnectionInterceptorFactoryChain fc = new TcpConnectionInterceptorFactoryChain();
fc.setInterceptors(new TcpConnectionInterceptorFactory[]
{new HelloWorldInterceptorFactory(),
new HelloWorldInterceptorFactory()});
fc.setInterceptors(new TcpConnectionInterceptorFactory[] {
newInterceptorFactory(),
newInterceptorFactory()
});
ccf.setInterceptorFactoryChain(fc);
ccf.setSingleUse(true);
ccf.start();
@@ -1090,7 +1149,7 @@ public class TcpSendingMessageHandlerTests {
@Test
public void testOutboundChannelAdapterWithinChain() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
"TcpOutboundChannelAdapterWithinChainTests-context.xml", this.getClass());
AbstractServerConnectionFactory scf = ctx.getBean(AbstractServerConnectionFactory.class);
TestingUtilities.waitListening(scf, null);
@@ -1101,6 +1160,7 @@ public class TcpSendingMessageHandlerTests {
Message<?> m = inbound.receive(1000);
assertNotNull(m);
assertEquals(testPayload, new String((byte[]) m.getPayload()));
ctx.destroy();
}
@Test
@@ -1109,6 +1169,7 @@ public class TcpSendingMessageHandlerTests {
AbstractConnectionFactory mockCcf = mock(AbstractClientConnectionFactory.class);
Mockito.doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
throw new SocketException("Failed to connect");
}

View File

@@ -20,9 +20,11 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.integration.support.MessageBuilder;
/**
* @author Gary Russell
@@ -50,12 +52,8 @@ public class HelloWorldInterceptor extends TcpConnectionInterceptorSupport {
public HelloWorldInterceptor() {
}
/**
* @param hello
* @param world
*/
public HelloWorldInterceptor(String hello, String world) {
super();
public HelloWorldInterceptor(String hello, String world, ApplicationEventPublisher applicationEventPublisher) {
super(applicationEventPublisher);
this.hello = hello;
this.world = world;
}

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.integration.ip.tcp.connection;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
/**
* @author Gary Russell
@@ -22,12 +25,14 @@ package org.springframework.integration.ip.tcp.connection;
*
*/
public class HelloWorldInterceptorFactory implements
TcpConnectionInterceptorFactory {
TcpConnectionInterceptorFactory, ApplicationEventPublisherAware {
private String hello = "Hello";
private String world = "world!";
private volatile ApplicationEventPublisher applicationEventPublisher;
public HelloWorldInterceptorFactory() {
}
@@ -40,9 +45,14 @@ public class HelloWorldInterceptorFactory implements
this.world = world;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public TcpConnectionInterceptorSupport getInterceptor() {
return new HelloWorldInterceptor(hello, world);
return new HelloWorldInterceptor(this.hello, this.world, this.applicationEventPublisher);
}

View File

@@ -2,7 +2,7 @@ log4j.rootCategory=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%c{1}: %m%n
log4j.appender.stdout.layout.ConversionPattern=%d %c{1} [%t] : %m%n
log4j.category.org.springframework.integration=WARN
log4j.category.org.springframework.integration.ip=WARN

Some files were not shown because too many files have changed in this diff Show More