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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
channels.autoCreate=true
|
||||
messagingTemplate.lateReply.logging.level=warn
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -171,7 +171,7 @@ public class SpelTransformerIntegrationTests {
|
||||
|
||||
@Override
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return new Class[] {Foo.class};
|
||||
return new Class<?>[] {Foo.class};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#channels.autoCreate=false
|
||||
messagingTemplate.lateReply.logging.level=error
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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[]{}));
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user