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

Conflicts:
	spring-integration-core/src/test/java/org/springframework/integration/aggregator/AggregatorTests.java
	spring-integration-core/src/test/java/org/springframework/integration/aggregator/ConcurrentAggregatorTests.java
	spring-integration-core/src/test/java/org/springframework/integration/aggregator/CorrelatingMessageHandlerTests.java
	spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java
	spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java
	spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisInboundChannelAdapterParserTests.java
	spring-integration-redis/src/test/java/org/springframework/integration/redis/store/DelayerHandlerRescheduleIntegrationTests.java
	spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageGroupStoreTests.java
	spring-integration-redis/src/test/java/org/springframework/integration/redis/store/RedisMessageStoreTests.java

Resolved
This commit is contained in:
Gary Russell
2013-10-24 09:18:00 -04:00
66 changed files with 2091 additions and 1112 deletions

1
.gitignore vendored
View File

@@ -26,3 +26,4 @@ spring-integration-jms/activemq-data/
spring-integration-samples/loanshark/application.log*
target
vf.gf.dmn-*
/atlassian-ide-plugin.xml

View File

@@ -28,7 +28,7 @@ Once you've completed the web form, simply add the following in a comment on you
## Fork the Repository
1. Go to [https://github.com/SpringSource/spring-integration](https://github.com/SpringSource/spring-integration)
1. Go to [https://github.com/spring-projects/spring-integration](https://github.com/spring-projects/spring-integration)
2. Hit the "fork" button and choose your own github account as the target
3. For more detail see [http://help.github.com/fork-a-repo/](http://help.github.com/fork-a-repo/)
@@ -38,7 +38,7 @@ Once you've completed the web form, simply add the following in a comment on you
2. `cd spring-integration`
3. `git remote show`
_you should see only 'origin' - which is the fork you created for your own github account_
4. `git remote add upstream git@github.com:SpringSource/spring-integration.git`
4. `git remote add upstream git@github.com:spring-projects/spring-integration.git`
5. `git remote show`
_you should now see 'upstream' in addition to 'origin' where 'upstream' is the SpringSource repository from which releases are built_
6. `git fetch --all`
@@ -233,4 +233,4 @@ Add a JIRA issue link to your first commit comment of the pull request on the la
[help documentation]: http://help.github.com/send-pull-requests
[JIRA issue tracker]: https://jira.springsource.org/browse/INT
[checking out and building]: https://github.com/SpringSource/spring-integration#checking-out-and-building
[checking out and building]: https://github.com/spring-projects/spring-integration#checking-out-and-building

View File

@@ -5,7 +5,7 @@ Spring Integration
To check out the project and build from source, do the following:
git clone git://github.com/SpringSource/spring-integration.git
git clone git://github.com/spring-projects/spring-integration.git
cd spring-integration
./gradlew build

View File

@@ -16,9 +16,9 @@ ext {
linkHomepage = 'http://www.springintegration.org/'
linkCi = 'https://build.springsource.org/browse/INT'
linkIssue = 'https://jira.springsource.org/browse/INT'
linkScmUrl = 'https://github.com/SpringSource/spring-integration'
linkScmConnection = 'git://github.com/SpringSource/spring-integration.git'
linkScmDevConnection = 'git@github.com:SpringSource/spring-integration.git'
linkScmUrl = 'https://github.com/spring-projects/spring-integration'
linkScmConnection = 'git://github.com/spring-projects/spring-integration.git'
linkScmDevConnection = 'git@github.com:spring-projects/spring-integration.git'
}
allprojects {
@@ -57,13 +57,16 @@ subprojects { subproject ->
log4jVersion = '1.2.12'
mockitoVersion = '1.9.5'
eaioUUIDVersion = '3.2'
ftpServerVersion = '1.0.6'
springVersionDefault = '4.0.0.M3'
springVersion = project.hasProperty('springVersion') ? getProperty('springVersion') : springVersionDefault
springAmqpVersion = '1.2.0.RELEASE'
springDataMongoVersion = '1.1.1.RELEASE'
springDataRedisVersion = '1.0.5.RELEASE'
springDataRedisVersion = '1.1.0.RELEASE'
lettuceVersion = '2.3.3'
springGemfireVersion = '1.3.1.RELEASE'
springSecurityVersion = '3.1.3.RELEASE'
springSocialTwitterVersion = '1.0.5.RELEASE'
@@ -240,6 +243,7 @@ project('spring-integration-ftp') {
compile "org.springframework:spring-context-support:$springVersion"
compile("javax.activation:activation:$javaxActivationVersion", optional)
testCompile project(":spring-integration-test")
testCompile "org.apache.ftpserver:ftpserver-core:$ftpServerVersion"
}
}
@@ -436,6 +440,7 @@ project('spring-integration-redis') {
exclude group: 'org.springframework', module: 'spring-tx'
}
testCompile project(":spring-integration-test")
testCompile "com.lambdaworks:lettuce:$lettuceVersion"
}
}
@@ -538,6 +543,7 @@ project('spring-integration-twitter') {
testCompile project(":spring-integration-test")
testCompile project(":spring-integration-redis")
testCompile project(":spring-integration-redis").sourceSets.test.output
testCompile "com.lambdaworks:lettuce:$lettuceVersion"
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -34,9 +35,10 @@ import org.springframework.util.xml.DomUtils;
/**
* Parser for the <gateway/> element.
*
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
*/
public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
@@ -51,9 +53,10 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
@Override
protected String getBeanClassName(Element element) {
return IntegrationNamespaceUtils.BASE_PACKAGE + ".gateway.GatewayProxyFactoryBean";
return GatewayProxyFactoryBean.class.getName();
}
@Override
protected boolean shouldGenerateIdAsFallback() {
return true;
}
@@ -62,6 +65,7 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
protected boolean isEligibleAttribute(String attributeName) {
return !ObjectUtils.containsElement(referenceAttributes, attributeName)
&& !ObjectUtils.containsElement(innerAttributes, attributeName)
&& !("default-payload-expression".equals(attributeName))
&& super.isEligibleAttribute(attributeName);
}
@@ -79,7 +83,7 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "request-channel", "defaultRequestChannel");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "defaultReplyChannel");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-timeout", "defaultRequestTimeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "defaultReplyTimeout");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "defaultReplyTimeout");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "async-executor");
}
@@ -88,6 +92,18 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
for (String attributeName : referenceAttributes) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, attributeName);
}
List<Element> invocationHeaders = DomUtils.getChildElementsByTagName(element, "default-header");
if (!CollectionUtils.isEmpty(invocationHeaders)
|| StringUtils.hasText(element.getAttribute("default-payload-expression"))) {
BeanDefinitionBuilder methodMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.gateway.GatewayMethodMetadata");
this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders);
IntegrationNamespaceUtils.setValueIfAttributeDefined(methodMetadataBuilder, element,
"default-payload-expression", "payloadExpression");
builder.addPropertyValue("globalMethodMetadata", methodMetadataBuilder.getBeanDefinition());
}
List<Element> elements = DomUtils.getChildElementsByTagName(element, "method");
ManagedMap<String, BeanDefinition> methodMetadataMap = null;
if (elements != null && elements.size() > 0) {
@@ -102,7 +118,7 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
methodMetadataBuilder.addPropertyValue("requestTimeout", methodElement.getAttribute("request-timeout"));
methodMetadataBuilder.addPropertyValue("replyTimeout", methodElement.getAttribute("reply-timeout"));
IntegrationNamespaceUtils.setValueIfAttributeDefined(methodMetadataBuilder, methodElement, "payload-expression");
List<Element> invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header");
invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header");
if (!CollectionUtils.isEmpty(invocationHeaders)) {
this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders);
}

View File

@@ -68,6 +68,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 2.0
*/
class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]>, BeanFactoryAware {
@@ -80,6 +81,8 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
private final Map<String, Expression> headerExpressions;
private final Map<String, Expression> globalHeaderExpressions;
private final List<MethodParameter> parameterList;
private volatile Expression payloadExpression;
@@ -96,9 +99,15 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
}
public GatewayMethodInboundMessageMapper(Method method, Map<String, Expression> headerExpressions) {
this(method, headerExpressions, null);
}
public GatewayMethodInboundMessageMapper(Method method, Map<String, Expression> headerExpressions,
Map<String, Expression> globalHeaderExpressions) {
Assert.notNull(method, "method must not be null");
this.method = method;
this.headerExpressions = headerExpressions;
this.globalHeaderExpressions = globalHeaderExpressions;
this.parameterList = getMethodParameterList(method);
this.payloadExpression = parsePayloadExpression(method);
}
@@ -194,23 +203,38 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
? MessageBuilder.fromMessage((Message<?>) messageOrPayload)
: MessageBuilder.withPayload(messageOrPayload);
builder.copyHeadersIfAbsent(headers);
// Explicit headers in XML override any @Header annotations...
if (!CollectionUtils.isEmpty(this.headerExpressions)) {
Map<String, Object> evaluatedHeaders = new HashMap<String, Object>();
for (Map.Entry<String, Expression> entry : this.headerExpressions.entrySet()) {
Object value = entry.getValue().getValue(methodInvocationEvaluationContext);
if (value != null) {
evaluatedHeaders.put(entry.getKey(), value);
}
}
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.headerExpressions);
builder.copyHeaders(evaluatedHeaders);
}
// ...whereas global (default) headers do not...
if (!CollectionUtils.isEmpty(this.globalHeaderExpressions)) {
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.globalHeaderExpressions);
builder.copyHeadersIfAbsent(evaluatedHeaders);
}
return builder.build();
}
private Map<String, Object> evaluateHeaders(EvaluationContext methodInvocationEvaluationContext, Map<String, Expression> headerExpressions) {
Map<String, Object> evaluatedHeaders = new HashMap<String, Object>();
for (Map.Entry<String, Expression> entry : headerExpressions.entrySet()) {
Object value = entry.getValue().getValue(methodInvocationEvaluationContext);
if (value != null) {
evaluatedHeaders.put(entry.getKey(), value);
}
}
return evaluatedHeaders;
}
private StandardEvaluationContext createMethodInvocationEvaluationContext(Object[] arguments) {
StandardEvaluationContext context = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
context.setVariable("args", arguments);
// TODO deprecated in 3.0/4.0 - retained for backwards compatibility
context.setVariable("method", this.method.getName());
context.setVariable("gatewayMethod", this.method);
return context;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import java.util.concurrent.Future;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.SimpleTypeConverter;
@@ -100,8 +101,9 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
private final Object initializationMonitor = new Object();
private Map<String, GatewayMethodMetadata> methodMetadataMap;
private volatile Map<String, GatewayMethodMetadata> methodMetadataMap;
private volatile GatewayMethodMetadata globalMethodMetadata;
/**
* Create a Factory whose service interface type can be configured by setter injection.
@@ -204,6 +206,10 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
this.methodMetadataMap = methodMetadataMap;
}
public void setGlobalMethodMetadata(GatewayMethodMetadata globalMethodMetadata) {
this.globalMethodMetadata = globalMethodMetadata;
}
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
@@ -339,7 +345,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
MessageChannel replyChannel = this.defaultReplyChannel;
Long requestTimeout = this.defaultRequestTimeout;
Long replyTimeout = this.defaultReplyTimeout;
String payloadExpression = null;
String payloadExpression = this.globalMethodMetadata != null ? this.globalMethodMetadata.getPayloadExpression()
: null;
Map<String, Expression> headerExpressions = null;
if (gatewayAnnotation != null) {
String requestChannelName = gatewayAnnotation.requestChannel();
@@ -387,7 +394,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
}
}
}
GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, headerExpressions);
GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, headerExpressions,
this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null);
if (StringUtils.hasText(payloadExpression)) {
messageMapper.setPayloadExpression(payloadExpression);
}

View File

@@ -515,7 +515,17 @@
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:sequence minOccurs="0" maxOccurs="1">
<xsd:element name="default-header" minOccurs="0" maxOccurs="unbounded" type="headerSubElementType">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Provides a mechanism to enrich the message with custom message headers. These default headers are created for
all methods on the service-interface (unless overridden by a specific method element).
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="method" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
@@ -530,7 +540,7 @@
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Provides mechanism to enrich content of the message with custom message headers. When this method is going to be invoked
Provides a mechanism to enrich the message with custom message headers. When this method is invoked,
the generated message will be enriched with these headers.
]]>
</xsd:documentation>
@@ -621,6 +631,17 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="default-payload-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An expression that will be used to generate the payload for all methods in the service interface
unless explicitly overriden by a method declaration. Variables include #args, #methodName, #methodString
and #methodObject; a bean resolver is also available, enabling expressions like "@someBean(#args)".
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="default-request-channel" type="xsd:string">
<xsd:annotation>
<xsd:documentation>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 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. You may obtain a copy of the License at
@@ -19,21 +19,18 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.MessageHandlingException;
import org.springframework.messaging.MessageHeaders;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
/**
* @author Mark Fisher
@@ -44,7 +41,7 @@ public class AggregatorTests {
private AggregatingMessageHandler aggregator;
private SimpleMessageStore store = new SimpleMessageStore(50);
private final SimpleMessageStore store = new SimpleMessageStore(50);
@Before
@@ -59,12 +56,12 @@ public class AggregatorTests {
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(3);
this.aggregator.handleMessage(message1);
this.aggregator.handleMessage(message2);
this.aggregator.handleMessage(message3);
latch.await(1000, TimeUnit.MILLISECONDS);
Message<?> reply = replyChannel.receive(2000);
Message<?> reply = replyChannel.receive(10000);
assertNotNull(reply);
assertEquals(reply.getPayload(), 105);
}
@@ -77,7 +74,7 @@ public class AggregatorTests {
Message<?> message = createMessage(3, "ABC", 2, 1, replyChannel, null);
this.aggregator.handleMessage(message);
this.store.expireMessageGroups(-10000);
Message<?> reply = replyChannel.receive(100);
Message<?> reply = replyChannel.receive(1000);
assertNull("No message should have been sent normally", reply);
Message<?> discardedMessage = discardChannel.receive(1000);
assertNotNull("A message should have been discarded", discardedMessage);
@@ -93,7 +90,7 @@ public class AggregatorTests {
this.aggregator.handleMessage(message1);
this.aggregator.handleMessage(message2);
this.store.expireMessageGroups(-10000);
Message<?> reply = replyChannel.receive(0);
Message<?> reply = replyChannel.receive(1000);
assertNotNull("A reply message should have been received", reply);
assertEquals(15, reply.getPayload());
}
@@ -115,11 +112,11 @@ public class AggregatorTests {
aggregator.handleMessage(message4);
aggregator.handleMessage(message2);
@SuppressWarnings("unchecked")
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(500);
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(1000);
assertNotNull(reply1);
assertThat(reply1.getPayload(), is(105));
@SuppressWarnings("unchecked")
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(500);
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(1000);
assertNotNull(reply2);
assertThat(reply2.getPayload(), is(2431));
}
@@ -133,14 +130,14 @@ public class AggregatorTests {
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel, null));
assertEquals(1, replyChannel.receive(100).getPayload());
assertEquals(1, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(3, 2, 1, 1, replyChannel, null));
assertEquals(3, replyChannel.receive(100).getPayload());
assertEquals(3, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(4, 3, 1, 1, replyChannel, null));
assertEquals(4, replyChannel.receive(100).getPayload());
assertEquals(4, replyChannel.receive(1000).getPayload());
// next message with same correllation ID is discarded
this.aggregator.handleMessage(createMessage(2, 1, 1, 1, replyChannel, null));
assertEquals(2, discardChannel.receive(100).getPayload());
assertEquals(2, discardChannel.receive(1000).getPayload());
}
@Test
@@ -152,15 +149,15 @@ public class AggregatorTests {
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel, null));
assertEquals(1, replyChannel.receive(100).getPayload());
assertEquals(1, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(2, 2, 1, 1, replyChannel, null));
assertEquals(2, replyChannel.receive(100).getPayload());
assertEquals(2, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(3, 3, 1, 1, replyChannel, null));
assertEquals(3, replyChannel.receive(100).getPayload());
assertEquals(3, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(4, 4, 1, 1, replyChannel, null));
assertEquals(4, replyChannel.receive(100).getPayload());
assertEquals(4, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(5, 1, 1, 1, replyChannel, null));
assertEquals(5, replyChannel.receive(100).getPayload());
assertEquals(5, replyChannel.receive(1000).getPayload());
assertNull(discardChannel.receive(0));
}
@@ -177,15 +174,13 @@ public class AggregatorTests {
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
Message<?> message4 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(4);
this.aggregator.handleMessage(message1);
this.aggregator.handleMessage(message2);
this.aggregator.handleMessage(message3);
this.aggregator.handleMessage(message4);
latch.await(1000, TimeUnit.MILLISECONDS);
// small wait to make sure the fourth message is received
Thread.sleep(10);
Message<?> reply = replyChannel.receive(0);
Message<?> reply = replyChannel.receive(10000);
assertNotNull("A message should be aggregated", reply);
assertThat(((Integer) reply.getPayload()), is(105));
}
@@ -197,18 +192,19 @@ public class AggregatorTests {
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
Message<?> message3 = createMessage(7, "ABC", 3, 3, replyChannel, null);
Message<?> message4 = createMessage(7, "ABC", 3, 3, replyChannel, null);
CountDownLatch latch = new CountDownLatch(4);
this.aggregator.handleMessage(message1);
this.aggregator.handleMessage(message3);
// duplicated sequence number, either message3 or message4 should be rejected
this.aggregator.handleMessage(message4);
this.aggregator.handleMessage(message2);
latch.await(1000, TimeUnit.MILLISECONDS);
Message<?> reply = replyChannel.receive(0);
Message<?> reply = replyChannel.receive(10000);
assertNotNull("A message should be aggregated", reply);
assertThat(((Integer) reply.getPayload()), is(105));
}
private static Message<?> createMessage(Object payload, Object correlationId, int sequenceSize, int sequenceNumber,
MessageChannel replyChannel, String predefinedId) {
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload).setCorrelationId(correlationId)
@@ -231,10 +227,4 @@ public class AggregatorTests {
}
private class NullReturningMessageProcessor implements MessageGroupProcessor {
public Object processMessageGroup(MessageGroup group) {
return null;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 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,31 +16,32 @@
package org.springframework.integration.aggregator;
import static org.hamcrest.CoreMatchers.is;
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 java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.MessageHandlingException;
import org.springframework.messaging.MessageHeaders;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
import static org.hamcrest.CoreMatchers.is;
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 org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHeaders;
/**
* @author Mark Fisher
@@ -53,7 +54,7 @@ public class ConcurrentAggregatorTests {
private AggregatingMessageHandler aggregator;
private MessageGroupStore store = new SimpleMessageStore();
private final MessageGroupStore store = new SimpleMessageStore();
@Before
@@ -76,7 +77,9 @@ public class ConcurrentAggregatorTests {
message2, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message3, latch));
latch.await(10000, TimeUnit.MILLISECONDS);
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertThat(latch.getCount(), is(0l));
Message<?> reply = replyChannel.receive(2000);
assertNotNull(reply);
@@ -101,7 +104,7 @@ public class ConcurrentAggregatorTests {
new AggregatorTestTask(this.aggregator, message1, latch).run();
new AggregatorTestTask(this.aggregator, message2, latch).run();
new AggregatorTestTask(this.aggregator, message3, latch).run();
Message<?> reply = replyChannel.receive(500);
Message<?> reply = replyChannel.receive(1000);
assertNotNull(reply);
assertEquals("123456789", reply.getPayload());
}
@@ -117,13 +120,15 @@ public class ConcurrentAggregatorTests {
AggregatorTestTask task = new AggregatorTestTask(this.aggregator,
message, latch);
this.taskExecutor.execute(task);
latch.await(200, TimeUnit.MILLISECONDS);
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertEquals("Task should have completed within timeout", 0, latch
.getCount());
Message<?> reply = replyChannel.receive(100);
Message<?> reply = replyChannel.receive(1000);
assertNull("No message should have been sent normally", reply);
this.store.expireMessageGroups(-10000);
Message<?> discardedMessage = discardChannel.receive(100);
Message<?> discardedMessage = discardChannel.receive(1000);
assertNotNull("A message should have been discarded", discardedMessage);
assertEquals(message, discardedMessage);
}
@@ -142,11 +147,13 @@ public class ConcurrentAggregatorTests {
message2, latch);
this.taskExecutor.execute(task1);
this.taskExecutor.execute(task2);
latch.await(300, TimeUnit.MILLISECONDS);
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertEquals("handlers should have been invoked within time limit", 0,
latch.getCount());
this.store.expireMessageGroups(-10000);
Message<?> reply = replyChannel.receive(100);
Message<?> reply = replyChannel.receive(1000);
assertNotNull("A reply message should have been received", reply);
assertEquals(15, reply.getPayload());
assertNull(task1.getException());
@@ -179,13 +186,15 @@ public class ConcurrentAggregatorTests {
message3, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message4, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
assertTrue(latch.await(10, TimeUnit.SECONDS));
@SuppressWarnings("unchecked")
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(500);
Message<Integer> reply1 = (Message<Integer>) replyChannel1.receive(1000);
assertNotNull(reply1);
assertThat(reply1.getPayload(), is(105));
@SuppressWarnings("unchecked")
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(500);
Message<Integer> reply2 = (Message<Integer>) replyChannel2.receive(1000);
assertNotNull(reply2);
assertThat(reply2.getPayload(), is(2431));
}
@@ -201,17 +210,17 @@ public class ConcurrentAggregatorTests {
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel,
null));
assertEquals(1, replyChannel.receive(100).getPayload());
assertEquals(1, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(3, 2, 1, 1, replyChannel,
null));
assertEquals(3, replyChannel.receive(100).getPayload());
assertEquals(3, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(4, 3, 1, 1, replyChannel,
null));
assertEquals(4, replyChannel.receive(100).getPayload());
assertEquals(4, replyChannel.receive(1000).getPayload());
// next message with same correlation ID is discarded
this.aggregator.handleMessage(createMessage(2, 1, 1, 1, replyChannel,
null));
assertEquals(2, discardChannel.receive(100).getPayload());
assertEquals(2, discardChannel.receive(1000).getPayload());
}
@Test
@@ -225,19 +234,19 @@ public class ConcurrentAggregatorTests {
this.aggregator.setDiscardChannel(discardChannel);
this.aggregator.handleMessage(createMessage(1, 1, 1, 1, replyChannel,
null));
assertEquals(1, replyChannel.receive(100).getPayload());
assertEquals(1, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(2, 2, 1, 1, replyChannel,
null));
assertEquals(2, replyChannel.receive(100).getPayload());
assertEquals(2, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(3, 3, 1, 1, replyChannel,
null));
assertEquals(3, replyChannel.receive(100).getPayload());
assertEquals(3, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(4, 4, 1, 1, replyChannel,
null));
assertEquals(4, replyChannel.receive(100).getPayload());
assertEquals(4, replyChannel.receive(1000).getPayload());
this.aggregator.handleMessage(createMessage(5, 1, 1, 1, replyChannel,
null));
assertEquals(5, replyChannel.receive(100).getPayload());
assertEquals(5, replyChannel.receive(1000).getPayload());
assertNull(discardChannel.receive(0));
}
@@ -266,12 +275,15 @@ public class ConcurrentAggregatorTests {
message3, latch));
this.taskExecutor.execute(new AggregatorTestTask(this.aggregator,
message4, latch));
latch.await(1000, TimeUnit.MILLISECONDS);
Message<?> reply = replyChannel.receive(100);
assertTrue(latch.await(10, TimeUnit.SECONDS));
Message<?> reply = replyChannel.receive(1000);
assertNotNull("A message should be aggregated", reply);
assertThat(((Integer) reply.getPayload()), is(105));
}
private static Message<?> createMessage(Object payload,
Object correlationId, int sequenceSize, int sequenceNumber,
MessageChannel replyChannel, String predefinedId) {
@@ -288,13 +300,13 @@ public class ConcurrentAggregatorTests {
private static class AggregatorTestTask implements Runnable {
private MessageHandler aggregator;
private final MessageHandler aggregator;
private Message<?> message;
private final Message<?> message;
private Exception exception;
private CountDownLatch latch;
private final CountDownLatch latch;
AggregatorTestTask(MessageHandler aggregator, Message<?> message,
CountDownLatch latch) {

View File

@@ -17,18 +17,20 @@
package org.springframework.integration.aggregator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.any;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
@@ -36,6 +38,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.internal.stubbing.answers.ThrowsException;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
@@ -57,7 +60,7 @@ public class CorrelatingMessageHandlerTests {
@Mock
private CorrelationStrategy correlationStrategy;
private ReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
private final ReleaseStrategy ReleaseStrategy = new SequenceSizeReleaseStrategy();
@Mock
private MessageGroupProcessor processor;
@@ -65,7 +68,7 @@ public class CorrelatingMessageHandlerTests {
@Mock
private MessageChannel outputChannel;
private MessageGroupStore store = new SimpleMessageStore();
private final MessageGroupStore store = new SimpleMessageStore();
@Before
@@ -152,10 +155,9 @@ public class CorrelatingMessageHandlerTests {
}
});
Thread.sleep(20);
assertEquals(0, store.expireMessageGroups(10000));
assertTrue(bothMessagesHandled.await(10, TimeUnit.SECONDS));
bothMessagesHandled.await();
assertEquals(0, store.expireMessageGroups(10000));
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@@ -12,6 +12,9 @@
*/
package org.springframework.integration.aggregator.scenarios;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -24,9 +27,6 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.support.MessageBuilder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Oleg Zhurakousky
*
@@ -79,11 +79,11 @@ public class AggregatorWithCustomReleaseStrategyTests {
assertTrue("Sends failed to complete", latch.await(10, TimeUnit.SECONDS));
Message<?> message = resultChannel.receive(10);
Message<?> message = resultChannel.receive(1000);
int counter = 0;
while(message != null){
counter++;
message = resultChannel.receive(10);
message = resultChannel.receive(1000);
}
assertEquals(600, counter);
}
@@ -119,10 +119,10 @@ public class AggregatorWithCustomReleaseStrategyTests {
assertTrue("Sends failed to complete", latch.await(10, TimeUnit.SECONDS));
Message<?> message = resultChannel.receive(10);
Message<?> message = resultChannel.receive(1000);
int counter = 0;
while(message != null && ++counter < 7200){
message = resultChannel.receive(10);
message = resultChannel.receive(1000);
}
assertEquals(7200, counter);
}

View File

@@ -5,10 +5,17 @@
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:gateway id="sampleGateway"
service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Bar"
default-request-channel="requestChannelBaz"/>
<int:gateway id="sampleGateway"
service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Bar"
default-request-channel="requestChannelBaz">
<int:default-header name="name" expression="#gatewayMethod.name"/>
<int:default-header name="string" expression="#gatewayMethod.toString()"/>
<int:default-header name="object" expression="#gatewayMethod"/>
<int:method name="baz">
<int:header name="name" value="overrideGlobal"/>
</int:method>
</int:gateway>
<int:channel id="requestChannelFoo"/>
<int:channel id="requestChannelBar"/>
<int:channel id="requestChannelBaz"/>

View File

@@ -16,43 +16,90 @@
package org.springframework.integration.gateway;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Gary Russell
*/
public class GatewayInterfaceTests {
@Test
public void testWithServiceSuperclassAnnotatedMethod(){
public void testWithServiceSuperclassAnnotatedMethod() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
final Method fooMethod = Foo.class.getMethod("foo", String.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertThat((String) message.getHeaders().get("name"), equalTo("foo"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod));
assertThat((String) message.getPayload(), equalTo("hello"));
called.set(true);
}
};
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.foo("hello");
verify(handler, times(1)).handleMessage(Mockito.any(Message.class));
assertTrue(called.get());
}
@Test
public void testWithServiceAnnotatedMethod(){
public void testWithServiceSuperclassAnnotatedMethodOverridePE() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests2-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
final Method fooMethod = Foo.class.getMethod("foo", String.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertThat((String) message.getHeaders().get("name"), equalTo("foo"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod));
assertThat((String) message.getPayload(), equalTo("foo"));
called.set(true);
}
};
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.foo("hello");
assertTrue(called.get());
}
@Test
public void testWithServiceAnnotatedMethod() {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBar", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
@@ -63,18 +110,57 @@ public class GatewayInterfaceTests {
}
@Test
public void testWithServiceSuperclassUnAnnotatedMethod(){
public void testWithServiceSuperclassUnAnnotatedMethod() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
final Method bazMethod = Foo.class.getMethod("baz", String.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertThat((String) message.getHeaders().get("name"), equalTo("overrideGlobal"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.baz(java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(bazMethod));
assertThat((String) message.getPayload(), equalTo("hello"));
called.set(true);
}
};
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.baz("hello");
verify(handler, times(1)).handleMessage(Mockito.any(Message.class));
assertTrue(called.get());
}
@Test
public void testWithServiceCastAsSuperclassAnnotatedMethod(){
public void testWithServiceUnAnnotatedMethodGlobalHeaderDoesntOverride() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
final Method quxMethod = Bar.class.getMethod("qux", String.class, String.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertThat((String) message.getHeaders().get("name"), equalTo("arg1"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Bar.qux(java.lang.String,java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(quxMethod));
assertThat((String) message.getPayload(), equalTo("hello"));
called.set(true);
}
};
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.qux("hello", "arg1");
assertTrue(called.get());
}
@Test
public void testWithServiceCastAsSuperclassAnnotatedMethod() {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
@@ -85,7 +171,7 @@ public class GatewayInterfaceTests {
}
@Test
public void testWithServiceCastAsSuperclassUnAnnotatedMethod(){
public void testWithServiceCastAsSuperclassUnAnnotatedMethod() {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
@@ -96,7 +182,7 @@ public class GatewayInterfaceTests {
}
@Test
public void testWithServiceHashcode() throws Exception{
public void testWithServiceHashcode() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
@@ -107,7 +193,7 @@ public class GatewayInterfaceTests {
}
@Test
public void testWithServiceToString(){
public void testWithServiceToString() {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
@@ -118,7 +204,7 @@ public class GatewayInterfaceTests {
}
@Test
public void testWithServiceEquals() throws Exception{
public void testWithServiceEquals() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
@@ -137,7 +223,7 @@ public class GatewayInterfaceTests {
}
@Test
public void testWithServiceGetClass(){
public void testWithServiceGetClass() {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
@@ -160,9 +246,11 @@ public class GatewayInterfaceTests {
public void baz(String payload);
}
public static interface Bar extends Foo{
public static interface Bar extends Foo {
@Gateway(requestChannel="requestChannelBar")
public void bar(String payload);
public void qux(String payload, @Header("name") String nameHeader);
}
public static class NotAnInterface {

View File

@@ -0,0 +1,23 @@
<?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:gateway id="sampleGateway"
service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Bar"
default-request-channel="requestChannelBaz" default-payload-expression="'foo'">
<int:default-header name="name" expression="#gatewayMethod.name"/>
<int:default-header name="string" expression="#gatewayMethod.toString()"/>
<int:default-header name="object" expression="#gatewayMethod"/>
<int:method name="baz">
<int:header name="name" value="overrideGlobal"/>
</int:method>
</int:gateway>
<int:channel id="requestChannelFoo"/>
<int:channel id="requestChannelBar"/>
<int:channel id="requestChannelBaz"/>
</beans>

View File

@@ -17,6 +17,7 @@ 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.ExpressionFactoryBean;
@@ -53,7 +54,12 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
this.configureFilter(builder, element, parserContext);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "remote-file-separator");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "local-directory");
BeanDefinition localDirExpressionDef = IntegrationNamespaceUtils
.createExpressionDefinitionFromValueOrExpression("local-directory", "local-directory-expression",
parserContext, element, false);
builder.addPropertyValue("localDirectoryExpression", localDirExpressionDef);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-local-directory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "rename-expression");

View File

@@ -31,6 +31,7 @@ import java.util.Set;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.file.FileHeaders;
@@ -65,22 +66,27 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
* Enumeration of commands supported by the gateways.
*/
public static enum Command {
/**
* List remote files.
*/
LS("ls"),
/**
* Retrieve a remote file.
*/
GET("get"),
/**
* Remove a remote file (path - including wildcards).
*/
RM("rm"),
/**
* Retrieve multiple files matching a wildcard path.
*/
MGET("mget"),
/**
* Move (rename) a remote file.
*/
@@ -111,34 +117,46 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
*
*/
public static enum Option {
/**
* Don't return full file information; just the name (ls).
*/
NAME_ONLY("-1"),
/**
* Include directories {@code .} and {@code ..} in the results (ls).
* Include files beginning with {@code .}, including directories {@code .} and {@code ..} in the results (ls).
*/
ALL("-a"),
/**
* Do not sort the results (ls with NAME_ONLY).
*/
NOSORT("-f"),
/**
* Include directories in the results (ls).
*/
SUBDIRS("-dirs"),
/**
* Include links in the results (ls).
*/
LINKS("-links"),
/**
* Preserve the server timestamp (get, mget).
*/
PRESERVE_TIMESTAMP("-P"),
/**
* Throw an exception if no files returned (mget).
*/
EXCEPTION_WHEN_EMPTY("-x");
EXCEPTION_WHEN_EMPTY("-x"),
/**
* Recursive (ls, mget)
*/
RECURSIVE("-R");
private String option;
@@ -170,7 +188,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
private volatile String remoteFileSeparator = "/";
private volatile File localDirectory;
private volatile Expression localDirectoryExpression;
private volatile boolean autoCreateLocalDirectory = true;
@@ -225,7 +243,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
* @param localDirectory the localDirectory to set
*/
public void setLocalDirectory(File localDirectory) {
this.localDirectory = localDirectory;
if (localDirectory != null) {
this.localDirectoryExpression = new LiteralExpression(localDirectory.getAbsolutePath());
}
}
public void setLocalDirectoryExpression(Expression localDirectoryExpression) {
this.localDirectoryExpression = localDirectoryExpression;
}
/**
@@ -265,35 +289,43 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
protected void onInit() {
super.onInit();
Assert.notNull(this.command, "command must not be null");
if (Command.RM.equals(this.command) || Command.MGET.equals(this.command) ||
if (Command.RM.equals(this.command) ||
Command.GET.equals(this.command)) {
Assert.isNull(this.filter, "Filters are not supported with the rm, get, and mget commands");
Assert.isNull(this.filter, "Filters are not supported with the rm and get commands");
}
if (Command.GET.equals(this.command)
|| Command.MGET.equals(this.command)) {
Assert.notNull(this.localDirectory, "localDirectory must not be null");
try {
if (!this.localDirectory.exists()) {
if (this.autoCreateLocalDirectory) {
if (logger.isDebugEnabled()) {
logger.debug("The '" + this.localDirectory + "' directory doesn't exist; Will create.");
Assert.notNull(this.localDirectoryExpression, "localDirectory must not be null");
if (this.localDirectoryExpression instanceof LiteralExpression) {
File localDirectory = new File(this.localDirectoryExpression.getExpressionString());
try {
if (!localDirectory.exists()) {
if (this.autoCreateLocalDirectory) {
if (logger.isDebugEnabled()) {
logger.debug("The '" + localDirectory + "' directory doesn't exist; Will create.");
}
if (!localDirectory.mkdirs()) {
throw new IOException("Failed to make local directory: " + localDirectory);
}
}
if (!this.localDirectory.mkdirs()) {
throw new IOException("Failed to make local directory: " + this.localDirectory);
else {
throw new FileNotFoundException(localDirectory.getName());
}
}
else {
throw new FileNotFoundException(this.localDirectory.getName());
}
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new MessagingException(
"Failure during initialization of: " + this.getComponentType(), e);
}
}
catch (RuntimeException e) {
throw e;
}
catch (Exception e) {
throw new MessagingException(
"Failure during initialization of: " + this.getComponentType(), e);
}
}
if (Command.MGET.equals(this.command)) {
Assert.isTrue(!(this.options.contains(Option.SUBDIRS)),
"Cannot use " + Option.SUBDIRS.toString() + " when using 'mget' use " + Option.RECURSIVE.toString() +
" to obtain files in subdirectories");
}
if (this.getBeanFactory() != null) {
this.fileNameProcessor.setBeanFactory(this.getBeanFactory());
@@ -341,12 +373,9 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
private Object doGet(Message<?> requestMessage, Session<F> session) throws IOException {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = getRemoteFilename(remoteFilePath);
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
if (remoteDir.length() == 0) {
remoteDir = this.remoteFileSeparator;
}
File payload = get(requestMessage, session, remoteFilePath, remoteFilename, true);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
File payload = this.get(requestMessage, session, remoteDir, remoteFilePath, remoteFilename, true);
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
@@ -355,12 +384,9 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
private Object doMget(Message<?> requestMessage, Session<F> session) throws IOException {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = getRemoteFilename(remoteFilePath);
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
if (remoteDir.length() == 0) {
remoteDir = this.remoteFileSeparator;
}
List<File> payload = mGet(requestMessage, session, remoteDir, remoteFilename);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
List<File> payload = this.mGet(requestMessage, session, remoteDir, remoteFilename);
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
@@ -369,12 +395,9 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
private Object doRm(Message<?> requestMessage, Session<F> session) throws IOException {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = getRemoteFilename(remoteFilePath);
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
if (remoteDir.length() == 0) {
remoteDir = this.remoteFileSeparator;
}
boolean payload = rm(session, remoteFilePath);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
boolean payload = this.rm(session, remoteFilePath);
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
@@ -383,14 +406,12 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
private Object doMv(Message<?> requestMessage, Session<F> session) throws IOException {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = getRemoteFilename(remoteFilePath);
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
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");
if (remoteDir.length() == 0) {
remoteDir = this.remoteFileSeparator;
}
mv(session, remoteFilePath, remoteFileNewPath);
this.mv(session, remoteFilePath, remoteFileNewPath);
return MessageBuilder.withPayload(Boolean.TRUE)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
@@ -399,21 +420,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
protected List<?> ls(Session<F> session, String dir) throws IOException {
List<F> lsFiles = new ArrayList<F>();
F[] files = session.list(dir);
if (!ObjectUtils.isEmpty(files)) {
Collection<F> filteredFiles = this.filterFiles(files);
for (F file : filteredFiles) {
if (file != null) {
if (this.options.contains(Option.SUBDIRS) || !isDirectory(file)) {
lsFiles.add(file);
}
}
}
}
else {
return lsFiles;
}
List<F> lsFiles = listFilesInRemoteDir(session, dir, "");
if (!this.options.contains(Option.LINKS)) {
purgeLinks(lsFiles);
}
@@ -442,6 +449,32 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
}
private List<F> listFilesInRemoteDir(Session<F> session, String directory, String subDirectory) throws IOException {
List<F> lsFiles = new ArrayList<F>();
F[] files = session.list(directory + subDirectory);
boolean recursion = this.options.contains(Option.RECURSIVE);
if (!ObjectUtils.isEmpty(files)) {
Collection<F> filteredFiles = this.filterFiles(files);
for (F file : filteredFiles) {
String fileName = this.getFilename(file);
if (file != null) {
if (this.options.contains(Option.SUBDIRS) || !this.isDirectory(file)) {
if (recursion && StringUtils.hasText(subDirectory)) {
lsFiles.add(enhanceNameWithSubDirectory(file, subDirectory));
}
else {
lsFiles.add(file);
}
}
if (recursion && this.isDirectory(file) && !(".".equals(fileName)) && !("..".equals(fileName))) {
lsFiles.addAll(listFilesInRemoteDir(session, directory, subDirectory + fileName + this.remoteFileSeparator));
}
}
}
}
return lsFiles;
}
protected final List<F> filterFiles(F[] files) {
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
}
@@ -467,21 +500,25 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
/**
* Copy a remote file to the configured local directory.
*
*
* @param message
* @param session
* @param remoteFilePath
* @throws IOException
* @param remoteDir
*@param remoteFilePath @throws IOException
*/
protected File get(Message<?> message, Session<F> session, String remoteFilePath, String remoteFilename, boolean lsFirst)
protected File get(Message<?> message, Session<F> session, String remoteDir, String remoteFilePath, String remoteFilename, boolean lsFirst)
throws IOException {
F[] files = null;
if (lsFirst) {
files = session.list(remoteFilePath);
if (files == null) {
throw new MessagingException("Session returned null when listing " + remoteFilePath);
}
if (files.length != 1 || isDirectory(files[0]) || isLink(files[0])) {
throw new MessagingException(remoteFilePath + " is not a file");
}
}
File localFile = new File(this.localDirectory, this.generateLocalFileName(message, remoteFilename));
File localFile = new File(this.generateLocalDirectory(message, remoteDir), this.generateLocalFileName(message, remoteFilename));
if (!localFile.exists()) {
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
File tempFile = new File(tempFileName);
@@ -520,7 +557,23 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
protected List<File> mGet(Message<?> message, Session<F> session, String remoteDirectory,
String remoteFilename) throws IOException {
String path = generateFullPath(remoteDirectory, remoteFilename);
if (this.options.contains(Option.RECURSIVE)) {
if (logger.isWarnEnabled() && !("*".equals(remoteFilename))) {
logger.warn("File name pattern must be '*' when using recursion");
}
if (this.options.contains(Option.NAME_ONLY)) {
this.options.remove(Option.NAME_ONLY);
}
return mGetWithRecursion(message, session, remoteDirectory, remoteFilename);
}
else {
return mGetWithoutRecursion(message, session, remoteDirectory, remoteFilename);
}
}
private List<File> mGetWithoutRecursion(Message<?> message, Session<F> session, String remoteDirectory,
String remoteFilename) throws IOException {
String path = this.generateFullPath(remoteDirectory, remoteFilename);
String[] fileNames = session.listNames(path);
if (fileNames == null) {
fileNames = new String[0];
@@ -534,17 +587,50 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
File file;
if (fileName.contains(this.remoteFileSeparator) &&
fileName.startsWith(remoteDirectory)) { // the server returned the full path
file = this.get(message, session, fileName,
file = this.get(message, session, remoteDirectory, fileName,
fileName.substring(fileName.lastIndexOf(this.remoteFileSeparator)), false);
}
else {
file = this.get(message, session, generateFullPath(remoteDirectory, fileName), fileName, false);
file = this.get(message, session, remoteDirectory,
this.generateFullPath(remoteDirectory, fileName), fileName, false);
}
files.add(file);
}
return files;
}
private List<File> mGetWithRecursion(Message<?> message, Session<F> session, String remoteDirectory,
String remoteFilename) throws IOException {
List<File> files = new ArrayList<File>();
@SuppressWarnings("unchecked")
List<AbstractFileInfo<F>> fileNames = (List<AbstractFileInfo<F>>) this.ls(session, remoteDirectory);
if (fileNames.size() == 0 && this.options.contains(Option.EXCEPTION_WHEN_EMPTY)) {
throw new MessagingException("No files found at " + remoteDirectory
+ " with pattern " + remoteFilename);
}
for (AbstractFileInfo<F> lsEntry : fileNames) {
String fullFileName = remoteDirectory + this.getFilename(lsEntry);
/*
* With recursion, the filename might contain subdirectory information
* normalize each file separately.
*/
String fileName = this.getRemoteFilename(fullFileName);
String actualRemoteDirectory = this.getRemoteDirectory(fullFileName, fileName);
File file = this.get(message, session, actualRemoteDirectory,
fullFileName, fileName, false);
files.add(file);
}
return files;
}
private String getRemoteDirectory(String remoteFilePath, String remoteFilename) {
String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf(remoteFilename));
if (remoteDir.length() == 0) {
remoteDir = this.remoteFileSeparator;
}
return remoteDir;
}
private String generateFullPath(String remoteDirectory, String remoteFilename) {
String path;
if (this.remoteFileSeparator.equals(remoteDirectory)) {
@@ -588,6 +674,17 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
session.rename(remoteFilePath, remoteFileNewPath);
}
private File generateLocalDirectory(Message<?> message, String remoteDirectory) {
EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
evaluationContext.setVariable("remoteDirectory", remoteDirectory);
// TODO Change 'desiredResultType' as 'File.class' after fix of SPR-10953.
File localDir = new File(this.localDirectoryExpression.getValue(evaluationContext, message, String.class));
if (!localDir.exists()) {
Assert.isTrue(localDir.mkdirs(), "Failed to make local directory: " + localDir);
}
return localDir;
}
private String generateLocalFileName(Message<?> message, String remoteFileName){
if (this.localFilenameGeneratorExpression != null){
EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
@@ -603,8 +700,11 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
abstract protected String getFilename(F file);
abstract protected String getFilename(AbstractFileInfo<F> file);
abstract protected long getModified(F file);
abstract protected List<AbstractFileInfo<F>> asFileInfoList(Collection<F> files);
abstract protected F enhanceNameWithSubDirectory(F file, String directory);
}

View File

@@ -16,10 +16,13 @@
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;
@@ -35,7 +38,6 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
/**
@@ -67,8 +69,6 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
private volatile boolean fileNameGeneratorSet;
private volatile File temporaryDirectory = new File(System.getProperty("java.io.tmpdir"));
private volatile String charset = "UTF-8";
private volatile String remoteFileSeparator = "/";
@@ -105,9 +105,11 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
return this.temporaryFileSuffix;
}
/**
* @deprecated This property is no longer used; byte[] and String payloads are written directly
*/
@Deprecated
public void setTemporaryDirectory(File temporaryDirectory) {
Assert.notNull(temporaryDirectory, "temporaryDirectory must not be null");
this.temporaryDirectory = temporaryDirectory;
}
protected boolean isUseTemporaryFileName() {
@@ -158,81 +160,87 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
File file = this.redeemForStorableFile(message);
if (file != null && file.exists()) {
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);
}
String fileName = this.fileNameGenerator.generateFileName(message);
this.sendFileToRemoteDirectory(file, temporaryRemoteDirectory, remoteDirectory, fileName, session);
fileName = this.fileNameGenerator.generateFileName(message);
this.sendFileToRemoteDirectory(inputStreamHolder.getStream(), temporaryRemoteDirectory, remoteDirectory, fileName, session);
}
catch (FileNotFoundException e) {
throw new MessageDeliveryException(message,
"File [" + file + "] not found in local working directory; it was moved or deleted unexpectedly.", e);
"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 [" + file + "] from local working directory to remote FTP directory.", e);
"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 [" + file + "]", e);
"Error handling message for file [" + inputStreamHolder.getName() + " -> " + fileName + "]", e);
}
finally {
if (!(message.getPayload() instanceof File)) {
// we created the File, so we need to delete it
if (file.exists()) {
try {
file.delete();
}
catch (Throwable t) {
// ignore
}
}
}
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 File redeemForStorableFile(Message<?> message) throws MessageDeliveryException {
private StreamHolder payloadToInputStream(Message<?> message) throws MessageDeliveryException {
try {
Object payload = message.getPayload();
File sendableFile = null;
InputStream dataInputStream = null;
String name = null;
if (payload instanceof File) {
sendableFile = (File) payload;
File inputFile = (File) payload;
if (inputFile.exists()) {
dataInputStream = new BufferedInputStream(new FileInputStream(inputFile));
name = inputFile.getAbsolutePath();
}
}
else if (payload instanceof byte[] || payload instanceof String) {
String tempFileName = this.fileNameGenerator.generateFileName(message) + ".tmp";
sendableFile = new File(this.temporaryDirectory, tempFileName); // will only create temp file for String/byte[]
byte[] bytes = null;
if (payload instanceof String) {
bytes = ((String) payload).getBytes(this.charset);
name = "String payload";
}
else {
bytes = (byte[]) payload;
name = "byte[] payload";
}
FileCopyUtils.copy(bytes, sendableFile);
dataInputStream = new ByteArrayInputStream(bytes);
}
else {
throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " +
"java.io.File, java.lang.String, and byte[]");
}
return sendableFile;
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(File file, String temporaryRemoteDirectory, String remoteDirectory, String fileName, Session<F> session)
throws FileNotFoundException, IOException {
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);
@@ -253,9 +261,8 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
}
FileInputStream fileInputStream = new FileInputStream(file);
try {
session.write(fileInputStream, tempFilePath);
session.write(inputStream, tempFilePath);
// then rename it to its final name if necessary
if (useTemporaryFileName){
session.rename(tempFilePath, remoteFilePath);
@@ -265,7 +272,7 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
throw new MessagingException("Failed to write to '" + tempFilePath + "' while uploading the file", e);
}
finally {
fileInputStream.close();
inputStream.close();
}
}
@@ -279,4 +286,25 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
return directoryPath;
}
private class StreamHolder {
private final InputStream stream;
private final String name;
private StreamHolder(InputStream stream, String name) {
this.stream = stream;
this.name = name;
}
public InputStream getStream() {
return stream;
}
public String getName() {
return name;
}
}
}

View File

@@ -27,7 +27,6 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@@ -90,8 +89,6 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
*/
private volatile boolean deleteRemoteFiles;
private volatile BeanFactory beanFactory;
/**
* Create a synchronizer with the {@link SessionFactory} used to acquire {@link Session} instances.
*/

View File

@@ -86,21 +86,6 @@ public class RemoteFileOutboundGatewayTests {
}
}
@Test
public void testBadFilterMGet() throws Exception {
SessionFactory sessionFactory = mock(SessionFactory.class);
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
(sessionFactory, "mget", "payload");
gw.setFilter(new TestPatternFilter(""));
try {
gw.onInit();
fail("Exception expected");
}
catch (IllegalArgumentException e) {
assertTrue(e.getMessage().startsWith("Filters are not supported"));
}
}
@Test
public void testBadFilterRm() throws Exception {
SessionFactory sessionFactory = mock(SessionFactory.class);
@@ -389,9 +374,6 @@ public class RemoteFileOutboundGatewayTests {
assertEquals("foo/bar", madeDirs.get(1));
}
/**
* @return
*/
public TestLsEntry[] fileList() {
TestLsEntry[] files = new TestLsEntry[6];
files[0] = new TestLsEntry("f2", 123, false, false, 1234, "-r--r--r--");
@@ -424,6 +406,83 @@ public class RemoteFileOutboundGatewayTests {
out.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
}
public TestLsEntry[] level1List() {
return new TestLsEntry[] {
new TestLsEntry("f1", 123, false, false, 1234, "-r--r--r--"),
new TestLsEntry("d1", 0, true, false, 12345, "drw-r--r--"),
new TestLsEntry("f2", 12345, false, false, 123456, "-rw-r--r--")
};
}
public TestLsEntry[] level2List() {
return new TestLsEntry[] {
new TestLsEntry("d2", 0, true, false, 12345, "drw-r--r--"),
new TestLsEntry("f3", 12345, false, false, 123456, "-rw-r--r--")
};
}
public TestLsEntry[] level3List() {
return new TestLsEntry[] {
new TestLsEntry("f4", 12345, false, false, 123456, "-rw-r--r--")
};
}
@Test
public void testLs_f_R() throws Exception {
SessionFactory sessionFactory = mock(SessionFactory.class);
Session session = mock(Session.class);
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
(sessionFactory, "ls", "payload");
gw.setOptions("-f -R");
gw.afterPropertiesSet();
when(sessionFactory.getSession()).thenReturn(session);
TestLsEntry[] level1 = level1List();
TestLsEntry[] level2 = level2List();
TestLsEntry[] level3 = level3List();
when(session.list("testremote/x/")).thenReturn(level1);
when(session.list("testremote/x/d1/")).thenReturn(level2);
when(session.list("testremote/x/d1/d2/")).thenReturn(level3);
@SuppressWarnings("unchecked")
Message<List<TestLsEntry>> out = (Message<List<TestLsEntry>>) gw
.handleRequestMessage(new GenericMessage<String>("testremote/x"));
assertEquals(4, out.getPayload().size());
assertEquals("f1", out.getPayload().get(0).getFilename());
assertEquals("d1/d2/f4", out.getPayload().get(1).getFilename());
assertEquals("d1/f3", out.getPayload().get(2).getFilename());
assertEquals("f2", out.getPayload().get(3).getFilename());
assertEquals("testremote/x/",
out.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
}
@Test
public void testLs_f_R_dirs() throws Exception {
SessionFactory sessionFactory = mock(SessionFactory.class);
Session session = mock(Session.class);
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
(sessionFactory, "ls", "payload");
gw.setOptions("-f -R -dirs");
gw.afterPropertiesSet();
when(sessionFactory.getSession()).thenReturn(session);
TestLsEntry[] level1 = level1List();
TestLsEntry[] level2 = level2List();
TestLsEntry[] level3 = level3List();
when(session.list("testremote/x/")).thenReturn(level1);
when(session.list("testremote/x/d1/")).thenReturn(level2);
when(session.list("testremote/x/d1/d2/")).thenReturn(level3);
@SuppressWarnings("unchecked")
Message<List<TestLsEntry>> out = (Message<List<TestLsEntry>>) gw
.handleRequestMessage(new GenericMessage<String>("testremote/x"));
assertEquals(6, out.getPayload().size());
assertEquals("f1", out.getPayload().get(0).getFilename());
assertEquals("d1", out.getPayload().get(1).getFilename());
assertEquals("d1/d2", out.getPayload().get(2).getFilename());
assertEquals("d1/d2/f4", out.getPayload().get(3).getFilename());
assertEquals("d1/f3", out.getPayload().get(4).getFilename());
assertEquals("f2", out.getPayload().get(5).getFilename());
assertEquals("testremote/x/",
out.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
}
@Test
public void testLs_None() throws Exception {
SessionFactory sessionFactory = mock(SessionFactory.class);
@@ -775,6 +834,11 @@ class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<Te
return file.getFilename();
}
@Override
protected String getFilename(AbstractFileInfo<TestLsEntry> file) {
return file.getFilename();
}
@Override
protected long getModified(TestLsEntry file) {
return file.getModified();
@@ -786,18 +850,24 @@ class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<Te
return new ArrayList<AbstractFileInfo<TestLsEntry>>(files);
}
@Override
protected TestLsEntry enhanceNameWithSubDirectory(TestLsEntry file, String directory) {
file.setFilename(directory + file.getFilename());
return file;
}
}
class TestLsEntry extends AbstractFileInfo<TestLsEntry> {
private final String filename;
private final int size;
private volatile String filename;
private final long size;
private final boolean dir;
private final boolean link;
private final long modified;
private final String permissions;
public TestLsEntry(String filename, int size, boolean dir, boolean link,
public TestLsEntry(String filename, long size, boolean dir, boolean link,
long modified, String permissions) {
this.filename = filename;
this.size = size;
@@ -835,6 +905,10 @@ class TestLsEntry extends AbstractFileInfo<TestLsEntry> {
return this;
}
public void setFilename(String filename) {
this.filename = filename;
}
}
class TestPatternFilter extends AbstractSimplePatternFileListFilter<TestLsEntry>{

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 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.
@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.List;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.remote.AbstractFileInfo;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
import org.springframework.integration.file.remote.session.SessionFactory;
@@ -28,7 +29,7 @@ import org.springframework.integration.ftp.session.FtpFileInfo;
/**
* Outbound Gateway for performing remote file operations via FTP/FTPS.
*
*
* @author Gary Russell
* @since 2.1
*/
@@ -54,6 +55,11 @@ public class FtpOutboundGateway extends AbstractRemoteFileOutboundGateway<FTPFil
return file.getName();
}
@Override
protected String getFilename(AbstractFileInfo<FTPFile> file) {
return file.getFilename();
}
@Override
protected long getModified(FTPFile file) {
return file.getTimestamp().getTimeInMillis();
@@ -69,4 +75,11 @@ public class FtpOutboundGateway extends AbstractRemoteFileOutboundGateway<FTPFil
}
@Override
protected FTPFile enhanceNameWithSubDirectory(FTPFile file, String directory) {
file.setName(directory + file.getName());
return file;
}
}

View File

@@ -415,6 +415,23 @@
Identifies directory path (e.g.,
"/local/mytransfers") where file will be
transferred TO.
This attribute is mutually exclusive with 'local-directory-expression'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="local-directory-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specifies SpEL expression to
generate the directory path where file will be
transferred TO, when using 'get' and 'mget' commands.
The root object of the SpEL evaluation is the request Message,
but the name of the source
remote directory is also provided as the 'remoteDirectory' variable.
For example, a valid expression might be:
"'/local/' + #remoteDirectory.toUpperCase() + headers.foo".
Only used with 'get' and 'mget' commands.
This attribute is mutually exclusive with 'local-directory'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -0,0 +1,241 @@
/*
* 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;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.Authentication;
import org.apache.ftpserver.ftplet.AuthenticationFailedException;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.User;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.apache.ftpserver.usermanager.impl.ConcurrentLoginPermission;
import org.apache.ftpserver.usermanager.impl.TransferRatePermission;
import org.apache.ftpserver.usermanager.impl.WritePermission;
import org.junit.rules.TemporaryFolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.ftp.session.DefaultFtpSessionFactory;
import org.springframework.integration.test.util.SocketUtils;
/**
* Embedded FTP Server for test cases; exposes an associated session factory
* as a @Bean.
*
* @author Artem Bilan
* @author Gary Russell
* @since 3.0
*/
@Configuration
public class TesFtpServer {
private final int ftpPort = SocketUtils.findAvailableServerSocket();
private final TemporaryFolder ftpFolder;
private final TemporaryFolder localFolder;
private volatile File ftpRootFolder;
private volatile File sourceFtpDirectory;
private volatile File targetFtpDirectory;
private volatile File sourceLocalDirectory;
private volatile File targetLocalDirectory;
private volatile FtpServer server;
public TesFtpServer(final String root) {
this.ftpFolder = new TemporaryFolder() {
@Override
public void create() throws IOException {
super.create();
ftpRootFolder = this.newFolder(root);
sourceFtpDirectory = new File(ftpRootFolder, "ftpSource");
sourceFtpDirectory.mkdir();
File file = new File(sourceFtpDirectory, "ftpSource1.txt");
file.createNewFile();
file = new File(sourceFtpDirectory, "ftpSource2.txt");
file.createNewFile();
File subSourceFtpDirectory = new File(sourceFtpDirectory, "subFtpSource");
subSourceFtpDirectory.mkdir();
file = new File(subSourceFtpDirectory, "subFtpSource1.txt");
file.createNewFile();
targetFtpDirectory = new File(ftpRootFolder, "ftpTarget");
targetFtpDirectory.mkdirs();
}
};
this.localFolder = new TemporaryFolder() {
@Override
public void create() throws IOException {
super.create();
File rootFolder = this.newFolder(root);
sourceLocalDirectory = new File(rootFolder, "localSource");
sourceLocalDirectory.mkdirs();
File file = new File(sourceLocalDirectory, "localSource1.txt");
file.createNewFile();
file = new File(sourceLocalDirectory, "localSource2.txt");
file.createNewFile();
File subSourceLocalDirectory = new File(sourceLocalDirectory, "subLocalSource");
subSourceLocalDirectory.mkdir();
file = new File(subSourceLocalDirectory, "subLocalSource1.txt");
file.createNewFile();
targetLocalDirectory = new File(rootFolder, "localTarget");
targetLocalDirectory.mkdirs();
}
};
}
public File getSourceFtpDirectory() {
return sourceFtpDirectory;
}
public File getTargetFtpDirectory() {
return targetFtpDirectory;
}
public File getSourceLocalDirectory() {
return sourceLocalDirectory;
}
public File getTargetLocalDirectory() {
return targetLocalDirectory;
}
public String getTargetLocalDirectoryName() {
return targetLocalDirectory.getAbsolutePath() + File.separator;
}
@Bean
public DefaultFtpSessionFactory ftpSessionFactory() {
DefaultFtpSessionFactory factory = new DefaultFtpSessionFactory();
factory.setHost("localhost");
factory.setPort(this.ftpPort);
factory.setUsername("foo");
factory.setPassword("foo");
return factory;
}
@PostConstruct
public void before() throws Throwable {
this.ftpFolder.create();
this.localFolder.create();
FtpServerFactory serverFactory = new FtpServerFactory();
serverFactory.setUserManager(new TestUserManager(this.ftpRootFolder.getAbsolutePath()));
ListenerFactory factory = new ListenerFactory();
factory.setPort(ftpPort);
serverFactory.addListener("default", factory.createListener());
server = serverFactory.createServer();
server.start();
}
@PreDestroy
public void after() {
this.server.stop();
this.ftpFolder.delete();
this.localFolder.delete();
}
public static void recursiveDelete(File file) {
File[] files = file.listFiles();
if (files != null) {
for (File each : files) {
recursiveDelete(each);
}
}
file.delete();
}
private class TestUserManager implements UserManager {
private final BaseUser testUser;
private TestUserManager(String homeDirectory) {
this.testUser = new BaseUser();
this.testUser.setAuthorities(Arrays.asList(new ConcurrentLoginPermission(1024, 1024),
new WritePermission(),
new TransferRatePermission(1024, 1024)));
this.testUser.setHomeDirectory(homeDirectory);
this.testUser.setName("TEST_USER");
}
@Override
public User getUserByName(String s) throws FtpException {
return this.testUser;
}
@Override
public String[] getAllUserNames() throws FtpException {
return new String[]{"TEST_USER"};
}
@Override
public void delete(String s) throws FtpException {
}
@Override
public void save(User user) throws FtpException {
}
@Override
public boolean doesExist(String s) throws FtpException {
return true;
}
@Override
public User authenticate(Authentication authentication) throws AuthenticationFailedException {
return this.testUser;
}
@Override
public String getAdminName() throws FtpException {
return "admin";
}
@Override
public boolean isAdmin(String s) throws FtpException {
return s.equals("admin");
}
}
}

View File

@@ -17,15 +17,16 @@
package org.springframework.integration.ftp.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Iterator;
import java.util.Set;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -71,7 +72,6 @@ public class FtpOutboundChannelAdapterParserTests {
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "directoryExpressionProcessor"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectoryExpressionProcessor"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
Object sfProperty = TestUtils.getPropertyValue(handler, "sessionFactory");
assertEquals(DefaultFtpSessionFactory.class, sfProperty.getClass());
DefaultFtpSessionFactory sessionFactory = (DefaultFtpSessionFactory) sfProperty;

View File

@@ -77,7 +77,7 @@ public class FtpOutboundGatewayParserTests {
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
assertEquals(Command.LS, TestUtils.getPropertyValue(gateway, "command"));
@@ -100,7 +100,7 @@ public class FtpOutboundGatewayParserTests {
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
assertTrue(TestUtils.getPropertyValue(gateway, "sessionFactory") instanceof CachingSessionFactory);
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
assertEquals(Command.GET, TestUtils.getPropertyValue(gateway, "command"));
@SuppressWarnings("unchecked")

View File

@@ -17,7 +17,6 @@
package org.springframework.integration.ftp.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
@@ -47,7 +46,6 @@ public class FtpsOutboundChannelAdapterParserTests {
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
DefaultFtpsSessionFactory sf = TestUtils.getPropertyValue(handler, "sessionFactory", DefaultFtpsSessionFactory.class);
assertEquals("localhost", TestUtils.getPropertyValue(sf, "host"));
assertEquals(22, TestUtils.getPropertyValue(sf, "port"));

View File

@@ -18,8 +18,11 @@ package org.springframework.integration.ftp.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.io.File;
@@ -31,7 +34,10 @@ import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.junit.Before;
@@ -40,6 +46,7 @@ import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -53,6 +60,7 @@ import org.springframework.integration.file.remote.handler.FileTransferringMessa
import org.springframework.integration.ftp.session.AbstractFtpSessionFactory;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.util.FileCopyUtils;
/**
@@ -92,8 +100,11 @@ public class FtpOutboundTests {
});
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
handler.handleMessage(new GenericMessage<String>("hello"));
handler.handleMessage(new GenericMessage<String>("String data"));
assertTrue(file.exists());
byte[] inFile = FileCopyUtils.copyToByteArray(file);
assertEquals("String data", new String(inFile));
file.delete();
}
@Test
@@ -112,8 +123,11 @@ public class FtpOutboundTests {
});
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
handler.handleMessage(new GenericMessage<byte[]>("hello".getBytes()));
handler.handleMessage(new GenericMessage<byte[]>("byte[] data".getBytes()));
assertTrue(file.exists());
byte[] inFile = FileCopyUtils.copyToByteArray(file);
assertEquals("byte[] data", new String(inFile));
file.delete();
}
@Test
@@ -141,6 +155,41 @@ public class FtpOutboundTests {
assertTrue("destination file was not created", destFile.exists());
}
@Test
public void testHandleMissingFileMessage() throws Exception {
File targetDir = new File("remote-target-dir");
assertTrue("target directory does not exist: " + targetDir.getName(), targetDir.exists());
FileTransferringMessageHandler<FTPFile> handler = new FileTransferringMessageHandler<FTPFile>(sessionFactory);
handler.setRemoteDirectoryExpression(new LiteralExpression(targetDir.getName()));
handler.setFileNameGenerator(new FileNameGenerator() {
public String generateFileName(Message<?> message) {
return ((File)message.getPayload()).getName() + ".test";
}
});
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
File srcFile = new File(UUID.randomUUID() + ".txt");
Log logger = spy(TestUtils.getPropertyValue(handler, "logger", Log.class));
when(logger.isWarnEnabled()).thenReturn(true);
final AtomicReference<String> logged = new AtomicReference<String>();
doAnswer(new Answer<Object>(){
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
logged.set((String) invocation.getArguments()[0]);
invocation.callRealMethod();
return null;
}
}).when(logger).warn(Mockito.anyString());
new DirectFieldAccessor(handler).setPropertyValue("logger", logger);
handler.handleMessage(new GenericMessage<File>(srcFile));
assertNotNull(logged.get());
assertEquals("File " + srcFile.toString() + " does not exist", logged.get());
}
@Test //INT-2275
public void testFtpOutboundChannelAdapterInsideChain() throws Exception {
File targetDir = new File("remote-target-dir");

View File

@@ -0,0 +1,71 @@
<?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-ftp="http://www.springframework.org/schema/integration/ftp"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp
http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="ftpServer" class="org.springframework.integration.ftp.TesFtpServer">
<constructor-arg value="FtpServerOutboundTests"/>
</bean>
<int:channel id="output">
<int:queue/>
</int:channel>
<int:channel id="inboundGet"/>
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundGet"
command="get"
expression="payload"
local-directory-expression="@ftpServer.targetLocalDirectoryName + #remoteDirectory.toUpperCase()"
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
<int:channel id="invalidDirExpression"/>
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="invalidDirExpression"
command="get"
expression="payload"
local-directory-expression="T(java.io.File).separator + #remoteDirectory + '?:'"
reply-channel="output"/>
<int:channel id="inboundMGet"/>
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMGet"
command="mget"
expression="payload"
local-directory-expression="@ftpServer.targetLocalDirectoryName + #remoteDirectory"
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
<int:channel id="inboundMGetRecursive"/>
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMGetRecursive"
command="mget"
expression="payload"
command-options="-R"
local-directory-expression="@ftpServer.targetLocalDirectoryName + #remoteDirectory"
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
<int:channel id="inboundMGetRecursiveFiltered"/>
<int-ftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMGetRecursiveFiltered"
command="mget"
expression="payload"
command-options="-R"
filename-regex="(subFtpSource|.*1.txt)"
local-directory-expression="@ftpServer.targetLocalDirectoryName + #remoteDirectory"
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
</beans>

View File

@@ -0,0 +1,173 @@
/*
* 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.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.File;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.ftp.TesFtpServer;
import org.springframework.integration.message.GenericMessage;
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 FtpServerOutboundTests {
@Autowired
public TesFtpServer ftpServer;
@Autowired
private PollableChannel output;
@Autowired
private DirectChannel inboundGet;
@Autowired
private DirectChannel invalidDirExpression;
@Autowired
private DirectChannel inboundMGet;
@Autowired
private DirectChannel inboundMGetRecursive;
@Autowired
private DirectChannel inboundMGetRecursiveFiltered;
@Before
public void setup() {
TesFtpServer.recursiveDelete(ftpServer.getTargetLocalDirectory());
TesFtpServer.recursiveDelete(ftpServer.getTargetFtpDirectory());
}
@Test
public void testInt2866LocalDirectoryExpressionGET() {
String dir = "ftpSource/";
this.inboundGet.send(new GenericMessage<Object>(dir + "ftpSource1.txt"));
Message<?> result = this.output.receive(1000);
assertNotNull(result);
File localFile = (File) result.getPayload();
assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir.toUpperCase()));
dir = "ftpSource/subFtpSource/";
this.inboundGet.send(new GenericMessage<Object>(dir + "subFtpSource1.txt"));
result = this.output.receive(1000);
assertNotNull(result);
localFile = (File) result.getPayload();
assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir.toUpperCase()));
}
@Test
public void testInt2866InvalidLocalDirectoryExpression() {
try {
this.invalidDirExpression.send(new GenericMessage<Object>("/ftpSource/ftpSource1.txt"));
fail("Exception expected.");
}
catch (Exception e) {
Throwable cause = e.getCause();
assertThat(cause, Matchers.instanceOf(IllegalArgumentException.class));
assertThat(cause.getMessage(), Matchers.startsWith("Failed to make local directory"));
}
}
@Test
@SuppressWarnings("unchecked")
public void testInt2866LocalDirectoryExpressionMGET() {
String dir = "ftpSource/";
this.inboundMGet.send(new GenericMessage<Object>(dir + "*.txt"));
Message<?> result = this.output.receive(1000);
assertNotNull(result);
List<File> localFiles = (List<File>) result.getPayload();
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir));
}
dir = "ftpSource/subFtpSource/";
this.inboundMGet.send(new GenericMessage<Object>(dir + "*.txt"));
result = this.output.receive(1000);
assertNotNull(result);
localFiles = (List<File>) result.getPayload();
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir));
}
}
@Test
@SuppressWarnings("unchecked")
public void testInt3172LocalDirectoryExpressionMGETRecursive() {
String dir = "ftpSource/";
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
Message<?> result = this.output.receive(1000);
assertNotNull(result);
List<File> localFiles = (List<File>) result.getPayload();
assertEquals(3, localFiles.size());
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir));
}
assertThat(localFiles.get(2).getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir + "subFtpSource"));
}
@Test
@SuppressWarnings("unchecked")
public void testInt3172LocalDirectoryExpressionMGETRecursiveFiltered() {
String dir = "ftpSource/";
this.inboundMGetRecursiveFiltered.send(new GenericMessage<Object>(dir + "*"));
Message<?> result = this.output.receive(1000);
assertNotNull(result);
List<File> localFiles = (List<File>) result.getPayload();
// should have filtered ftpSource2.txt
assertEquals(2, localFiles.size());
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir));
}
assertThat(localFiles.get(1).getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir + "subFtpSource"));
}
}

View File

@@ -26,7 +26,7 @@ task generateSql {
doLast {
['hsqldb', 'h2', 'db2', 'derby', 'mysql', 'mysql-5_6_4',
'oracle10g', 'postgresql', 'sqlserver', 'sybase'].each { dbType ->
ant.vppcopy(todir: generatedResourcesDir, overwrite: 'true') {
ant.vppcopy(todir: generatedResourcesDir) {
config {
context {
property key: 'includes', value: 'src/main/sql'
@@ -45,3 +45,7 @@ task generateSql {
// tie schema generation to the build lifecycle
compileJava.dependsOn generateSql
task cleanSql (type: Delete) {
delete fileTree(dir: 'src/main/resources/org/springframework/integration/jdbc').include('*.sql').exclude('config', 'store/channel')
}

File diff suppressed because one or more lines are too long

View File

@@ -17,64 +17,79 @@ package org.springframework.integration.redis.channel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.util.ReflectionUtils;
/**
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class SubscribableRedisChannelTests extends RedisAvailableTests{
public class SubscribableRedisChannelTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void pubSubChanneTest() throws Exception{
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.setPort(7379);
connectionFactory.afterPropertiesSet();
public void pubSubChannelTest() throws Exception{
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
SubscribableRedisChannel channel = new SubscribableRedisChannel(connectionFactory, "si.test.channel");
channel.setBeanFactory(mock(BeanFactory.class));
channel.afterPropertiesSet();
channel.start();
MessageHandler handler = mock(MessageHandler.class);
RedisConnection connection = TestUtils.getPropertyValue(channel, "container.subscriptionTask.connection",
RedisConnection.class);
int n = 0;
while (n++ < 100 && !connection.isSubscribed()) {
Thread.sleep(100);
}
assertTrue(n < 100);
final CountDownLatch latch = new CountDownLatch(3);
MessageHandler handler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
latch.countDown();
}
};
channel.subscribe(handler);
channel.send(new GenericMessage<String>("1"));
channel.send(new GenericMessage<String>("2"));
channel.send(new GenericMessage<String>("3"));
Thread.sleep(1000);
verify(handler, times(3)).handleMessage(Mockito.any(Message.class));
channel.stop();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
@RedisAvailable
public void dispatcherHasNoSubscribersTest() throws Exception{
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.setPort(7379);
connectionFactory.afterPropertiesSet();
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
SubscribableRedisChannel channel = new SubscribableRedisChannel(connectionFactory, "si.test.channel.no.subs");
channel.setBeanName("dhnsChannel");

View File

@@ -12,8 +12,8 @@
<int-redis:publish-subscribe-channel id="redisChannel" topic-name="si.test.topic"
serializer="redisSerializer"/>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379"/>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
<bean id="redisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>

View File

@@ -17,11 +17,15 @@
package org.springframework.integration.redis.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
@@ -36,6 +40,7 @@ import org.springframework.integration.test.util.TestUtils;
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
*/
public class RedisChannelParserTests extends RedisAvailableTests{
@@ -44,8 +49,8 @@ public class RedisChannelParserTests extends RedisAvailableTests{
public void testPubSubChannelConfig(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("RedisChannelParserTests-context.xml", this.getClass());
SubscribableChannel redisChannel = context.getBean("redisChannel", SubscribableChannel.class);
JedisConnectionFactory connectionFactory =
TestUtils.getPropertyValue(redisChannel, "connectionFactory", JedisConnectionFactory.class);
RedisConnectionFactory connectionFactory =
TestUtils.getPropertyValue(redisChannel, "connectionFactory", RedisConnectionFactory.class);
RedisSerializer<?> redisSerializer = TestUtils.getPropertyValue(redisChannel, "serializer", RedisSerializer.class);
assertEquals(connectionFactory, context.getBean("redisConnectionFactory"));
assertEquals(redisSerializer, context.getBean("redisSerializer"));
@@ -65,20 +70,17 @@ public class RedisChannelParserTests extends RedisAvailableTests{
SubscribableChannel redisChannel = context.getBean("redisChannel", SubscribableChannel.class);
final Message<?> m = new GenericMessage<String>("Hello Redis");
final Marker marker = Mockito.mock(Marker.class);
final CountDownLatch latch = new CountDownLatch(1);
redisChannel.subscribe(new MessageHandler() {
public void handleMessage(Message<?> message) throws MessagingException {
assertEquals(m.getPayload(), message.getPayload());
marker.mark();
latch.countDown();
}
});
redisChannel.send(m);
Thread.sleep(1000);
Mockito.verify(marker, Mockito.times(1)).mark();
assertTrue(latch.await(2, TimeUnit.SECONDS));
context.stop();
}
interface Marker {
void mark();
}
}

View File

@@ -8,7 +8,7 @@
<int-redis:inbound-channel-adapter
id="adapter" topics="foo, bar" channel="receiveChannel" error-channel="testErrorChannel"
message-converter="testConverter"
message-converter="testConverter"
serializer="serializer"/>
<int:channel id="receiveChannel">
@@ -17,8 +17,8 @@
<int:channel id="testErrorChannel" />
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379" />
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
<bean id="testConverter"
@@ -27,7 +27,7 @@
<int-redis:inbound-channel-adapter
id="autoChannel" topics="foo, bar" error-channel="testErrorChannel"
message-converter="testConverter" />
<bean id="serializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<int:bridge input-channel="autoChannel" output-channel="nullChannel"/>

View File

@@ -21,18 +21,19 @@ import static org.junit.Assert.assertSame;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.messaging.MessageChannel;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.redis.inbound.RedisInboundChannelAdapter;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.support.converter.SimpleMessageConverter;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -44,7 +45,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class RedisInboundChannelAdapterParserTests extends RedisAvailableTests{
public class RedisInboundChannelAdapterParserTests extends RedisAvailableTests {
@Autowired
private ApplicationContext context;
@@ -71,16 +72,15 @@ public class RedisInboundChannelAdapterParserTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testInboundChannelAdapterMessaging() throws Exception{
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.setPort(7379);
connectionFactory.afterPropertiesSet();
public void testInboundChannelAdapterMessaging() throws Exception {
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
connectionFactory.getConnection().publish("foo".getBytes(), "Hello Redis from foo".getBytes());
Thread.sleep(1000);
QueueChannel receiveChannel = context.getBean("receiveChannel", QueueChannel.class);
assertEquals("Hello Redis from foo", receiveChannel.receive(1000).getPayload());
assertEquals("Hello Redis from foo", receiveChannel.receive(2000).getPayload());
connectionFactory.getConnection().publish("bar".getBytes(), "Hello Redis from bar".getBytes());
assertEquals("Hello Redis from bar", receiveChannel.receive(1000).getPayload());
assertEquals("Hello Redis from bar", receiveChannel.receive(2000).getPayload());
}
@Test

View File

@@ -21,8 +21,8 @@
<int:queue/>
</int:channel>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379"/>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
<bean id="testConverter" class="org.springframework.integration.redis.config.RedisOutboundChannelAdapterParserTests$TestMessageConverter"/>
@@ -30,7 +30,7 @@
<int:chain input-channel="redisOutboudChain">
<int-redis:outbound-channel-adapter topic="foo"/>
</int:chain>
<bean id="serializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</beans>

View File

@@ -16,9 +16,8 @@
<int:poller fixed-rate="2000" max-messages-per-poll="10"/>
</int-redis:store-inbound-channel-adapter>
<bean id="redisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379" />
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2011 the original author or authors
* Copyright 2007-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,11 +16,17 @@
package org.springframework.integration.redis.inbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.messaging.Message;
@@ -29,11 +35,6 @@ import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.test.util.TestUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Mark Fisher
* @since 2.1
@@ -42,7 +43,7 @@ public class RedisInboundChannelAdapterTests extends RedisAvailableTests{
private final Log logger = LogFactory.getLog(this.getClass());
@Test
@Test
@RedisAvailable
public void testRedisInboundChannelAdapter() throws Exception {
for (int iteration = 0; iteration < 10; iteration ++) {
@@ -55,9 +56,7 @@ public class RedisInboundChannelAdapterTests extends RedisAvailableTests{
String redisChannelName = "testRedisInboundChannelAdapterChannel";
QueueChannel channel = new QueueChannel();
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.setPort(7379);
connectionFactory.afterPropertiesSet();
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
RedisInboundChannelAdapter adapter = new RedisInboundChannelAdapter(connectionFactory);
adapter.setTopics("testRedisInboundChannelAdapterChannel");
@@ -87,7 +86,6 @@ public class RedisInboundChannelAdapterTests extends RedisAvailableTests{
assertEquals(numToTest, counter);
adapter.stop();
container.stop();
connectionFactory.destroy();
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,12 @@ package org.springframework.integration.redis.inbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.support.collections.RedisList;
import org.springframework.data.redis.support.collections.RedisZSet;
import org.springframework.messaging.Message;
@@ -34,6 +35,7 @@ import org.springframework.integration.redis.rules.RedisAvailableTests;
/**
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.2
*/
public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvailableTests{
@@ -42,7 +44,7 @@ public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvaila
@RedisAvailable
@SuppressWarnings("unchecked")
public void testListInboundConfiguration() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareList(jcf);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("list-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter spca = context.getBean("listAdapter", SourcePollingChannelAdapter.class);
@@ -64,7 +66,7 @@ public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvaila
@RedisAvailable
@SuppressWarnings("unchecked")
public void testListInboundConfigurationWithSynchronization() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareList(jcf);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("list-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter spca = context.getBean("listAdapterWithSynchronization", SourcePollingChannelAdapter.class);
@@ -87,7 +89,7 @@ public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvaila
@RedisAvailable
@SuppressWarnings("unchecked")
public void testListInboundConfigurationWithSynchronizationAndTemplate() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareList(jcf);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("list-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter spca = context.getBean("listAdapterWithSynchronizationAndRedisTemplate", SourcePollingChannelAdapter.class);
@@ -110,7 +112,7 @@ public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvaila
@RedisAvailable
@SuppressWarnings("unchecked")
public void testZsetInboundConfiguration(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareZset(jcf);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("zset-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter zsetAdapterNoScore =
@@ -136,7 +138,7 @@ public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvaila
@RedisAvailable
@SuppressWarnings("unchecked")
public void testZsetInboundConfigurationWithScoreRange(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareZset(jcf);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("zset-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter zsetAdapterWithScoreRange =
@@ -162,7 +164,7 @@ public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvaila
@RedisAvailable
@SuppressWarnings("unchecked")
public void testZsetInboundConfigurationWithSingleScore(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareZset(jcf);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("zset-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter zsetAdapterWithSingleScore =
@@ -188,7 +190,7 @@ public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvaila
@RedisAvailable
@SuppressWarnings("unchecked")
public void testZsetInboundConfigurationWithSingleScoreAndSynchronization() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareZset(jcf);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("zset-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter zsetAdapterWithSingleScoreAndSynchronization =
@@ -199,31 +201,33 @@ public class RedisStoreInboundChannelAdapterIntegrationTests extends RedisAvaila
QueueChannel redisChannel = context.getBean("redisChannel", QueueChannel.class);
QueueChannel otherRedisChannel = context.getBean("otherRedisChannel", QueueChannel.class);
// get all 13 presidents
zsetAdapterNoScore.start();
Message<RedisZSet<Object>> message = (Message<RedisZSet<Object>>) redisChannel.receive(1000);
assertNotNull(message);
assertEquals(13, message.getPayload().size());
zsetAdapterNoScore.stop();
Thread.sleep(1000);
// get only presidents for 18th century
zsetAdapterWithSingleScoreAndSynchronization.start();
message = (Message<RedisZSet<Object>>) otherRedisChannel.receive(1000);
assertNotNull(message);
assertEquals(2, message.getPayload().rangeByScore(18, 18).size());
zsetAdapterWithSingleScoreAndSynchronization.stop();
Thread.sleep(1000);
// ... however other elements are still available 13-2=11
zsetAdapterNoScore.start();
message = (Message<RedisZSet<Object>>) redisChannel.receive(1000);
assertNotNull(message);
assertEquals(11, message.getPayload().size());
int n = 0;
while(n++ < 100 && message.getPayload().size() != 11) {
Thread.sleep(100);
}
assertTrue(n < 100);
zsetAdapterNoScore.stop();
context.close();
}

View File

@@ -25,7 +25,7 @@
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int-redis:store-inbound-channel-adapter id="listAdapterWithSynchronizationAndRollback"
connection-factory="redisConnectionFactory"
key-expression="'presidents'"
@@ -35,7 +35,7 @@
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int:channel id="redisFailChannel"/>
<int-redis:store-inbound-channel-adapter id="listAdapterWithSynchronizationAndRedisTemplate"
@@ -47,12 +47,12 @@
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int:transaction-synchronization-factory id="syncFactory">
<int:after-commit expression="#resource.attributes['store'].rename('bar')"/>
<int:after-rollback expression="#store.rename('baz')"/>
</int:transaction-synchronization-factory>
<int-redis:store-inbound-channel-adapter id="listAdapterWithSynchronizationBeforeCommit"
redis-template="redisTemplate"
key-expression="'presidents'"
@@ -62,11 +62,11 @@
<int:transactional synchronization-factory="syncFactory2"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int:channel id="adapterErrors">
<int:queue/>
</int:channel>
<int:transaction-synchronization-factory id="syncFactory2">
<int:before-commit expression="5/0"/>
</int:transaction-synchronization-factory>
@@ -85,10 +85,10 @@
<int:queue/>
</int:channel>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379"/>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
</beans>

View File

@@ -13,7 +13,7 @@
channel="redisChannel"
auto-startup="false"
collection-type="ZSET">
<int:poller fixed-rate="1000" max-messages-per-poll="2"/>
<int:poller fixed-rate="1000"/>
</int-redis:store-inbound-channel-adapter>
<int-redis:store-inbound-channel-adapter id="zsetAdapterWithScoreRange"
@@ -31,7 +31,7 @@
channel="redisChannel"
auto-startup="false"
collection-type="ZSET">
<int:poller fixed-rate="1000" max-messages-per-poll="2"/>
<int:poller fixed-rate="1000"/>
</int-redis:store-inbound-channel-adapter>
<int-redis:store-inbound-channel-adapter id="zsetAdapterWithSingleScoreAndSynchronization"
@@ -40,11 +40,11 @@
channel="otherRedisChannel"
auto-startup="false"
collection-type="ZSET">
<int:poller fixed-rate="1000" max-messages-per-poll="2">
<int:poller fixed-rate="1000">
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int:transaction-synchronization-factory id="syncFactory">
<int:after-commit expression="payload.removeByScore(18, 18)"/>
</int:transaction-synchronization-factory>
@@ -57,10 +57,11 @@
<int:queue/>
</int:channel>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379"/>
<bean id="redisConnectionFactory"
class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2011 the original author or authors
* Copyright 2007-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,12 +16,15 @@
package org.springframework.integration.redis.outbound;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
@@ -31,24 +34,20 @@ import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.support.MessageBuilder;
import static org.junit.Assert.assertEquals;
/**
* @author Mark Fisher
* @since 2.1
*/
public class RedisPublishingMessageHandlerTests extends RedisAvailableTests{
public class RedisPublishingMessageHandlerTests extends RedisAvailableTests {
@Test
@Test
@RedisAvailable
public void testRedisPublishingMessageHandler() throws Exception {
int numToTest = 10;
String topic = "si.test.channel";
final CountDownLatch latch = new CountDownLatch(numToTest);
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.setPort(7379);
connectionFactory.afterPropertiesSet();
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
MessageListenerAdapter listener = new MessageListenerAdapter();
listener.setDelegate(new Listener(latch));
@@ -67,8 +66,7 @@ public class RedisPublishingMessageHandlerTests extends RedisAvailableTests{
for (int i = 0; i < numToTest; i++) {
handler.handleMessage(MessageBuilder.withPayload("test-" + i).build());
}
latch.await(3, TimeUnit.SECONDS);
assertEquals(0, latch.getCount());
assertTrue(latch.await(3, TimeUnit.SECONDS));
container.stop();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2012 the original author or authors
* Copyright 2007-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.
@@ -27,9 +27,9 @@ import java.util.Properties;
import java.util.Set;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@@ -63,7 +63,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testListWithKeyAsHeader(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisList<String> redisList =
new DefaultRedisList<String>("pepboys", this.initTemplate(jcf, new StringRedisTemplate()));
@@ -84,7 +84,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testListWithKeyAsHeaderSimple(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
StringRedisTemplate redisTemplate = new StringRedisTemplate();
RedisList<String> redisList =
@@ -103,7 +103,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testListWithProvidedKey(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisList<String> redisList =
new DefaultRedisList<String>("pepboys", this.initTemplate(jcf, new StringRedisTemplate()));
assertEquals(0, redisList.size());
@@ -123,7 +123,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testZsetSimplePayloadIncrement(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
StringRedisTemplate redisTemplate = new StringRedisTemplate();
RedisZSet<String> redisZSet =
@@ -148,7 +148,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testZsetSimplePayloadOverwrite(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
StringRedisTemplate redisTemplate = new StringRedisTemplate();
RedisZSet<String> redisZSet =
@@ -176,7 +176,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testZsetSimplePayloadIncrementBy2(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
StringRedisTemplate redisTemplate = new StringRedisTemplate();
RedisZSet<String> redisZSet =
@@ -204,7 +204,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testZsetSimplePayloadOverwriteWithHeaderScore(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
StringRedisTemplate redisTemplate = new StringRedisTemplate();
RedisZSet<String> redisZSet =
@@ -233,7 +233,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testMapToZsetWithProvidedKey(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisZSet<String> redisZset =
new DefaultRedisZSet<String>("presidents", this.initTemplate(jcf, new StringRedisTemplate()));
assertEquals(0, redisZset.size());
@@ -279,7 +279,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testMapToMapWithProvidedKey(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMap<String, String> redisMap =
new DefaultRedisMap<String, String>("pepboys",
this.initTemplate(jcf, new StringRedisTemplate()));
@@ -308,7 +308,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test(expected=MessageHandlingException.class) // map key is not provided
@RedisAvailable
public void testMapToMapAsSingleEntryWithKeyAsHeaderFail(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMap<String, Map<String, String>> redisMap =
new DefaultRedisMap<String, Map<String, String>>("pepboys",
this.initTemplate(jcf, new RedisTemplate<String, Map<String, Map<String, String>>>()));
@@ -330,7 +330,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test(expected=MessageHandlingException.class) // key is not provided
@RedisAvailable
public void testMapToMapNoKey(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisTemplate<String, Map<String, Map<String, String>>> redisTemplate = new RedisTemplate<String, Map<String, Map<String, String>>>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
@@ -354,7 +354,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testMapToMapAsSingleEntryWithKeyAsHeader(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisTemplate<String, Map<String, Map<String, String>>> redisTemplate = new RedisTemplate<String, Map<String, Map<String, String>>>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
@@ -384,7 +384,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testStoreSimpleStringInMap(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
StringRedisTemplate redisTemplate = new StringRedisTemplate();
RedisMap<String, String> redisMap =
new DefaultRedisMap<String, String>("bar",
@@ -406,7 +406,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testSetWithKeyAsHeader(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisSet<String> redisSet =
new DefaultRedisSet<String>("pepboys", this.initTemplate(jcf, new StringRedisTemplate()));
assertEquals(0, redisSet.size());
@@ -426,7 +426,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testSetWithKeyAsHeaderSimple(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
StringRedisTemplate redisTemplate = new StringRedisTemplate();
RedisSet<String> redisSet =
new DefaultRedisSet<String>("foo", this.initTemplate(jcf, redisTemplate));
@@ -445,7 +445,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testSetWithKeyAsHeaderNotParsed(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
@@ -468,7 +468,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testPojoIntoSet(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisSet<String> redisSet =
new DefaultRedisSet<String>("pepboys", this.initTemplate(jcf, new StringRedisTemplate()));
assertEquals(0, redisSet.size());
@@ -485,7 +485,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testProperties(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisProperties redisProperties =
new RedisProperties("pepboys", this.initTemplate(jcf, new StringRedisTemplate()));
@@ -508,7 +508,7 @@ public class RedisStoreOutboundChannelAdapterIntegrationTests extends RedisAvail
@Test
@RedisAvailable
public void testPropertiesSimple(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
StringRedisTemplate redisTemplate = new StringRedisTemplate();
RedisProperties redisProperties =
new RedisProperties("foo", this.initTemplate(jcf, redisTemplate));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@ import java.util.Set;
import org.junit.Test;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
@@ -58,7 +57,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testListWithListPayloadParsedAndProvidedKey() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisList<String> redisList =
new DefaultRedisList<String>(key, this.initTemplate(jcf, new StringRedisTemplate()));
@@ -86,7 +85,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testListWithListPayloadParsedAndProvidedKeyAsHeader() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisList<String> redisList =
new DefaultRedisList<String>(key, this.initTemplate(jcf, new StringRedisTemplate()));
@@ -114,7 +113,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@RedisAvailable
@Test(expected=MessageHandlingException.class)
public void testListWithListPayloadParsedAndNoKey() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisList<String> redisList =
new DefaultRedisList<String>(key, this.initTemplate(jcf, new RedisTemplate<String, String>()));
@@ -136,7 +135,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testListWithListPayloadAsSingleEntry() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisList<List<String>> redisList =
new DefaultRedisList<List<String>>(key, this.initTemplate(jcf, new RedisTemplate<String, List<String>>()));
@@ -167,7 +166,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testZsetWithListPayloadParsedAndProvidedKeyDefault() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisZSet<String> redisZset =
new DefaultRedisZSet<String>(key, this.initTemplate(jcf, new StringRedisTemplate()));
@@ -205,7 +204,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testZsetWithListPayloadParsedAndProvidedKeyScoreIncrement() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisZSet<String> redisZset =
new DefaultRedisZSet<String>(key, this.initTemplate(jcf, new StringRedisTemplate()));
@@ -246,7 +245,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testZsetWithListPayloadParsedAndProvidedKeyScoreIncrementAsStringHeader() {// see INT-2775
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisZSet<String> redisZset =
new DefaultRedisZSet<String>(key, this.initTemplate(jcf, new StringRedisTemplate()));
@@ -287,7 +286,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testZsetWithListPayloadAsSingleEntryAndHeaderKeyHeaderScore() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisZSet<List<String>> redisZset =
new DefaultRedisZSet<List<String>>(key, this.initTemplate(jcf, new RedisTemplate<String, List<String>>()));
@@ -320,7 +319,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testZsetWithMapPayloadParsedHeaderKey() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "presidents";
RedisZSet<String> redisZset =
new DefaultRedisZSet<String>(key, this.initTemplate(jcf, new StringRedisTemplate()));
@@ -362,7 +361,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testZsetWithMapPayloadPojoParsedHeaderKey() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "presidents";
RedisZSet<President> redisZset =
new DefaultRedisZSet<President>(key, this.initTemplate(jcf, new RedisTemplate<String, President>()));
@@ -405,7 +404,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testZsetWithMapPayloadPojoAsSingleEntryHeaderKey() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "presidents";
RedisZSet<Map<President, Double>> redisZset =
new DefaultRedisZSet<Map<President, Double>>(key, this.initTemplate(jcf, new RedisTemplate<String, Map<President, Double>>()));
@@ -435,7 +434,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test(expected=IllegalStateException.class)
@RedisAvailable
public void testListWithMapKeyExpression() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisStoreWritingMessageHandler handler =
new RedisStoreWritingMessageHandler(jcf);
@@ -447,7 +446,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test(expected=IllegalStateException.class)
@RedisAvailable
public void testSetWithMapKeyExpression() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisStoreWritingMessageHandler handler =
new RedisStoreWritingMessageHandler(jcf);
@@ -460,7 +459,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test(expected=IllegalStateException.class)
@RedisAvailable
public void testZsetWithMapKeyExpression() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisStoreWritingMessageHandler handler =
new RedisStoreWritingMessageHandler(jcf);
@@ -473,7 +472,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testMapWithMapKeyExpression() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisStoreWritingMessageHandler handler =
new RedisStoreWritingMessageHandler(jcf);
@@ -491,7 +490,7 @@ public class RedisStoreWritingMessageHandlerTests extends RedisAvailableTests{
@Test
@RedisAvailable
public void testPropertiesWithMapKeyExpression() {
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
String key = "foo";
RedisStoreWritingMessageHandler handler =
new RedisStoreWritingMessageHandler(jcf);

View File

@@ -61,8 +61,8 @@
map-key-expression="headers['baz']"
collection-type="PROPERTIES"/>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379"/>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,49 +15,71 @@
*/
package org.springframework.integration.redis.rules;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assume;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
*
* @author Artem Bilan
*/
public final class RedisAvailableRule implements MethodRule{
public final class RedisAvailableRule implements MethodRule {
private static final Log logger = LogFactory.getLog(RedisAvailableRule.class);
public static final int REDIS_PORT = 6379;
public static final int REDIS_PORT = 7379;
static ThreadLocal<LettuceConnectionFactory> connectionFactoryResource = new ThreadLocal<LettuceConnectionFactory>();
public Statement apply(final Statement base, final FrameworkMethod method, Object target) {
return new Statement(){
RedisAvailable redisAvailable = method.getAnnotation(RedisAvailable.class);
if (redisAvailable != null) {
LettuceConnectionFactory connectionFactory = null;
try {
connectionFactory = new LettuceConnectionFactory();
connectionFactory.setPort(REDIS_PORT);
connectionFactory.afterPropertiesSet();
connectionFactory.getConnection();
connectionFactoryResource.set(connectionFactory);
}
catch (Exception e) {
if (connectionFactory != null) {
connectionFactory.destroy();
}
return new Statement() {
@Override
public void evaluate() throws Throwable {
Assume.assumeTrue("Skipping test due to Redis not being available on port: " + REDIS_PORT, false);
}
};
}
@Override
public void evaluate() throws Throwable {
RedisAvailable redisAvailable = method.getAnnotation(RedisAvailable.class);
if (redisAvailable != null){
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.setPort(REDIS_PORT);
connectionFactory.afterPropertiesSet();
connectionFactory.getConnection();
} catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn(String.format("Redis is not available on " +
"port '%s'. Skipping the test.", REDIS_PORT));
base.evaluate();
}
finally {
LettuceConnectionFactory connectionFactory = connectionFactoryResource.get();
connectionFactoryResource.remove();
if (connectionFactory != null) {
connectionFactory.destroy();
}
return;
}
}
};
}
return new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
}
};
}
}

View File

@@ -21,7 +21,8 @@ import org.junit.Rule;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisCallback;
@@ -31,21 +32,20 @@ import org.springframework.data.redis.core.StringRedisTemplate;
/**
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*
*/
public class RedisAvailableTests {
@Rule
public RedisAvailableRule redisAvailableRule = new RedisAvailableRule();
@SuppressWarnings({ "rawtypes", "unchecked" })
public JedisConnectionFactory getConnectionFactoryForTest(){
JedisConnectionFactory jcf = new JedisConnectionFactory();
jcf.setPort(7379);
jcf.afterPropertiesSet();
RedisTemplate rt = new RedisTemplate<UUID, Object>();
rt.setConnectionFactory(jcf);
public RedisConnectionFactory getConnectionFactoryForTest(){
LettuceConnectionFactory connectionFactory = RedisAvailableRule.connectionFactoryResource.get();
RedisTemplate<UUID, Object> rt = new RedisTemplate<UUID, Object>();
rt.setConnectionFactory(connectionFactory);
rt.afterPropertiesSet();
rt.execute(new RedisCallback() {
rt.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
@@ -53,13 +53,13 @@ public class RedisAvailableTests {
return null;
}
});
return jcf;
return connectionFactory;
}
protected void prepareList(JedisConnectionFactory jcf){
protected void prepareList(RedisConnectionFactory connectionFactory){
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(jcf);
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
BoundListOperations<String, String> ops = redisTemplate.boundListOps("presidents");
@@ -80,10 +80,10 @@ public class RedisAvailableTests {
ops.rightPush("George Washington");
}
protected void prepareZset(JedisConnectionFactory jcf){
protected void prepareZset(RedisConnectionFactory connectionFactory){
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(jcf);
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
BoundZSetOperations<String, String> ops = redisTemplate.boundZSetOps("presidents");

View File

@@ -5,12 +5,8 @@
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">
<beans:bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<beans:property name="port" value="7379" />
</beans:bean>
<beans:bean id="messageStore" class="org.springframework.integration.redis.store.RedisMessageStore">
<beans:constructor-arg ref="redisConnectionFactory"/>
<beans:constructor-arg value="#{T(org.springframework.integration.redis.store.DelayerHandlerRescheduleIntegrationTests).connectionFactory}"/>
</beans:bean>
<channel id="output">

View File

@@ -21,14 +21,17 @@ import static org.junit.Assert.fail;
import java.util.concurrent.TimeUnit;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.handler.DelayHandler;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableRule;
import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
@@ -47,9 +50,22 @@ public class DelayerHandlerRescheduleIntegrationTests extends RedisAvailableTest
public static final String DELAYER_ID = "delayerWithRedisMS";
public static LettuceConnectionFactory connectionFactory;
@Rule
public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();
@BeforeClass
public static void setup() {
connectionFactory = new LettuceConnectionFactory();
connectionFactory.setPort(RedisAvailableRule.REDIS_PORT);
connectionFactory.afterPropertiesSet();
}
public static void tearDown() {
connectionFactory.destroy();
}
@Test
@RedisAvailable
public void testDelayerHandlerRescheduleWithRedisMessageStore() throws Exception {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2012 the original author or authors
* Copyright 2007-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,9 +35,7 @@ import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.history.MessageHistory;
@@ -47,6 +45,8 @@ import org.springframework.integration.redis.rules.RedisAvailableTests;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.SimpleMessageGroup;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
/**
* @author Oleg Zhurakousky
@@ -57,7 +57,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testNonExistingEmptyMessageGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
@@ -69,7 +69,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testMessageGroupUpdatedDateChangesWithEachAddedMessage() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
@@ -96,7 +96,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testMessageGroupWithAddedMessage() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
@@ -114,7 +114,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testRemoveMessageGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
@@ -141,7 +141,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testCompleteMessageGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
@@ -155,7 +155,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testLastReleasedSequenceNumber() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
@@ -169,7 +169,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testRemoveMessageFromTheGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
@@ -192,7 +192,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testWithMessageHistory() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
store.getMessageGroup(1);
@@ -219,7 +219,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testRemoveNonExistingMessageFromTheGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
MessageGroup messageGroup = store.getMessageGroup(1);
@@ -230,7 +230,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testRemoveNonExistingMessageFromNonExistingTheGroup() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
store.removeMessageFromGroup(1, new GenericMessage<String>("2"));
}
@@ -240,7 +240,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testMultipleInstancesOfGroupStore() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store1 = new RedisMessageStore(jcf);
RedisMessageStore store2 = new RedisMessageStore(jcf);
@@ -261,7 +261,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testIteratorOfMessageGroups() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store1 = new RedisMessageStore(jcf);
RedisMessageStore store2 = new RedisMessageStore(jcf);
@@ -303,7 +303,7 @@ public class RedisMessageGroupStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable @Ignore
public void testConcurrentModifications() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
final RedisMessageStore store1 = new RedisMessageStore(jcf);
final RedisMessageStore store2 = new RedisMessageStore(jcf);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2007-2011 the original author or authors
* Copyright 2007-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.
@@ -15,24 +15,24 @@
*/
package org.springframework.integration.redis.store;
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.Serializable;
import java.util.Properties;
import java.util.UUID;
import org.junit.Test;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.messaging.Message;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
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 org.springframework.messaging.Message;
/**
* @author Oleg Zhurakousky
@@ -42,62 +42,62 @@ public class RedisMessageStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testGetNonExistingMessage(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
public void testGetNonExistingMessage(){
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
Message<?> message = store.getMessage(UUID.randomUUID());
assertNull(message);
}
@Test
@RedisAvailable
public void testGetMessageCountWhenEmpty(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
public void testGetMessageCountWhenEmpty(){
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
assertEquals(0, store.getMessageCount());
}
@Test
@RedisAvailable
public void testAddStringMessage(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
public void testAddStringMessage(){
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
Message<String> stringMessage = new GenericMessage<String>("Hello Redis");
Message<String> storedMessage = store.addMessage(stringMessage);
assertNotSame(stringMessage, storedMessage);
assertEquals("Hello Redis", storedMessage.getPayload());
}
@Test
@RedisAvailable
public void testAddSerializableObjectMessage(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
public void testAddSerializableObjectMessage(){
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
Address address = new Address();
address.setAddress("1600 Pennsylvania Av, Washington, DC");
Person person = new Person(address, "Barak Obama");
Message<Person> objectMessage = new GenericMessage<Person>(person);
Message<Person> storedMessage = store.addMessage(objectMessage);
assertNotSame(objectMessage, storedMessage);
assertEquals("Barak Obama", storedMessage.getPayload().getName());
}
@Test(expected=IllegalArgumentException.class)
@RedisAvailable
public void testAddNonSerializableObjectMessage(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
public void testAddNonSerializableObjectMessage(){
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
Message<Foo> objectMessage = new GenericMessage<Foo>(new Foo());
store.addMessage(objectMessage);
}
@SuppressWarnings("unchecked")
@Test
@RedisAvailable
public void testAddAndGetStringMessage(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
public void testAddAndGetStringMessage(){
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
Message<String> stringMessage = new GenericMessage<String>("Hello Redis");
store.addMessage(stringMessage);
@@ -108,8 +108,8 @@ public class RedisMessageStoreTests extends RedisAvailableTests {
@SuppressWarnings("unchecked")
@Test
@RedisAvailable
public void testAddAndRemoveStringMessage(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
public void testAddAndRemoveStringMessage(){
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
Message<String> stringMessage = new GenericMessage<String>("Hello Redis");
store.addMessage(stringMessage);
@@ -118,19 +118,19 @@ public class RedisMessageStoreTests extends RedisAvailableTests {
assertEquals("Hello Redis", retrievedMessage.getPayload());
assertNull(store.getMessage(stringMessage.getHeaders().getId()));
}
@Test
@RedisAvailable
public void testWithMessageHistory() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
public void testWithMessageHistory() throws Exception{
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMessageStore store = new RedisMessageStore(jcf);
Message<?> message = new GenericMessage<String>("Hello");
DirectChannel fooChannel = new DirectChannel();
fooChannel.setBeanName("fooChannel");
DirectChannel barChannel = new DirectChannel();
barChannel.setBeanName("barChannel");
message = MessageHistory.write(message, fooChannel);
message = MessageHistory.write(message, barChannel);
store.addMessage(message);
@@ -142,7 +142,7 @@ public class RedisMessageStoreTests extends RedisAvailableTests {
assertEquals("fooChannel", fooChannelHistory.get("name"));
assertEquals("channel", fooChannelHistory.get("type"));
}
@SuppressWarnings("serial")
public static class Person implements Serializable{
private Address address;
@@ -176,8 +176,8 @@ public class RedisMessageStoreTests extends RedisAvailableTests {
this.address = address;
}
}
public static class Foo{
}
}

View File

@@ -20,7 +20,8 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.integration.redis.rules.RedisAvailable;
@@ -36,7 +37,7 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testGetNonExistingKeyValue(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
String retrievedValue = metadataStore.get("does-not-exist");
assertNull(retrievedValue);
@@ -45,7 +46,7 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testPersistKeyValue(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
metadataStore.put("RedisMetadataStoreTests-Spring", "Integration");
@@ -59,7 +60,7 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
@RedisAvailable
public void testGetValueFromMetadataStore(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
metadataStore.put("RedisMetadataStoreTests-GetValue", "Hello Redis");
@@ -71,7 +72,7 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
@RedisAvailable
public void testPersistEmptyStringToMetadataStore(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
metadataStore.put("RedisMetadataStoreTests-PersistEmpty", "");
@@ -83,7 +84,7 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
@RedisAvailable
public void testPersistNullStringToMetadataStore(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
try {
@@ -101,7 +102,7 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testPersistWithEmptyKeyToMetadataStore(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
metadataStore.put("", "PersistWithEmptyKey");
@@ -112,7 +113,7 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testPersistWithNullKeyToMetadataStore(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
try {
@@ -129,7 +130,7 @@ public class RedisMetadataStoreTests extends RedisAvailableTests {
@Test
@RedisAvailable
public void testGetValueWithNullKeyFromMetadataStore(){
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisConnectionFactory jcf = this.getConnectionFactoryForTest();
RedisMetadataStore metadataStore = new RedisMetadataStore(jcf);
try {

View File

@@ -4,19 +4,19 @@
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:aggregator input-channel="inputChannel" output-channel="outputChannel" message-store="redisStore"/>
<int:channel id="outputChannel">
<int:queue/>
</int:channel>
<bean id="redisStore" class="org.springframework.integration.redis.store.RedisMessageStore">
<constructor-arg ref="redisConnectionFactory"/>
</bean>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379" />
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 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.
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.integration.file.remote.AbstractFileInfo;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
import org.springframework.integration.file.remote.session.SessionFactory;
@@ -29,7 +30,7 @@ import com.jcraft.jsch.ChannelSftp.LsEntry;
/**
* Outbound Gateway for performing remote file operations via SFTP.
*
*
* @author Gary Russell
* @since 2.1
*/
@@ -59,6 +60,11 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
return file.getFilename();
}
@Override
protected String getFilename(AbstractFileInfo<LsEntry> file) {
return file.getFilename();
}
@Override
protected List<AbstractFileInfo<LsEntry>> asFileInfoList(Collection<LsEntry> files) {
List<AbstractFileInfo<LsEntry>> canonicalFiles = new ArrayList<AbstractFileInfo<LsEntry>>();
@@ -73,4 +79,11 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
return ((long)file.getAttrs().getMTime()) * 1000;
}
@Override
protected LsEntry enhanceNameWithSubDirectory(LsEntry file, String directory) {
DirectFieldAccessor accessor = new DirectFieldAccessor(file);
accessor.setPropertyValue("filename", directory + file.getFilename());
return file;
}
}

View File

@@ -414,6 +414,23 @@
Identifies directory path (e.g.,
"/local/mytransfers") where file will be
transferred TO.
This attribute is mutually exclusive with 'local-directory-expression'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="local-directory-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specifies SpEL expression to
generate the directory path where file will be
transferred TO, when using 'get' and 'mget' commands.
The root object of the SpEL evaluation is the request Message,
but the name of the source
remote directory is also provided as the 'remoteDirectory' variable.
For example, a valid expression might be:
"'/local/' + #remoteDirectory.toUpperCase() + headers.foo".
Only used with 'get' and 'mget' commands.
This attribute is mutually exclusive with 'local-directory'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -17,16 +17,17 @@
package org.springframework.integration.sftp.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
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 java.util.Iterator;
import java.util.Set;
import org.junit.Test;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -75,7 +76,6 @@ public class OutboundChannelAdapterParserTests {
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectoryExpressionProcessor"));
assertEquals(context.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
CachingSessionFactory<?> sessionFactory = TestUtils.getPropertyValue(handler, "sessionFactory", CachingSessionFactory.class);
DefaultSftpSessionFactory clientFactory = TestUtils.getPropertyValue(sessionFactory, "sessionFactory", DefaultSftpSessionFactory.class);
assertEquals("localhost", TestUtils.getPropertyValue(clientFactory, "host"));
@@ -108,7 +108,6 @@ public class OutboundChannelAdapterParserTests {
String fileNameGeneratorExpression = (String) TestUtils.getPropertyValue(generator, "expression");
assertEquals("payload.getName() + '-foo'", fileNameGeneratorExpression);
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
assertNull(TestUtils.getPropertyValue(handler, "temporaryDirectoryExpressionProcessor"));
}

View File

@@ -75,7 +75,7 @@ public class SftpOutboundGatewayParserTests {
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
assertTrue(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class));
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
@@ -97,7 +97,7 @@ public class SftpOutboundGatewayParserTests {
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
assertTrue(TestUtils.getPropertyValue(gateway, "sessionFactory") instanceof CachingSessionFactory);
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
assertEquals(new File("local-test-dir"), TestUtils.getPropertyValue(gateway, "localDirectory"));
assertEquals("local-test-dir", TestUtils.getPropertyValue(gateway, "localDirectoryExpression.literalValue"));
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
assertEquals(Command.GET, TestUtils.getPropertyValue(gateway, "command"));
assertFalse(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class));

View File

@@ -108,8 +108,11 @@ public class SftpOutboundTests {
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
handler.handleMessage(new GenericMessage<String>("hello"));
handler.handleMessage(new GenericMessage<String>("String data"));
assertTrue(new File("remote-target-dir", "foo.txt").exists());
byte[] inFile = FileCopyUtils.copyToByteArray(file);
assertEquals("String data", new String(inFile));
file.delete();
}
@Test
@@ -128,8 +131,11 @@ public class SftpOutboundTests {
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
handler.handleMessage(new GenericMessage<byte[]>("hello".getBytes()));
handler.handleMessage(new GenericMessage<byte[]>("byte[] data".getBytes()));
assertTrue(new File("remote-target-dir", "foo.txt").exists());
byte[] inFile = FileCopyUtils.copyToByteArray(file);
assertEquals("byte[] data", new String(inFile));
file.delete();
}
@Test //INT-2275

View File

@@ -0,0 +1,80 @@
<?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-sftp="http://www.springframework.org/schema/integration/sftp"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
http://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<int:channel id="output">
<int:queue/>
</int:channel>
<int:channel id="inboundGet"/>
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundGet"
command="get"
expression="payload"
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory.toUpperCase()"
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
<int:channel id="invalidDirExpression"/>
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="invalidDirExpression"
command="get"
expression="payload"
local-directory-expression="T(java.io.File).separator + #remoteDirectory + '?:'"
reply-channel="output"/>
<int:channel id="inboundMGet"/>
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMGet"
command="mget"
expression="payload"
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory"
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
<int:channel id="inboundMGetRecursive"/>
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMGetRecursive"
command="mget"
expression="payload"
command-options="-R"
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory"
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
<int:channel id="inboundMGetRecursiveFiltered"/>
<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
request-channel="inboundMGetRecursiveFiltered"
command="mget"
expression="payload"
command-options="-R"
filename-regex="(subSftpSource|.*1.txt)"
local-directory-expression="'/tmp/sftpOutboundTests/' + #remoteDirectory"
local-filename-generator-expression="#remoteFileName.replaceFirst('ftpSource', 'localTarget')"
reply-channel="output"/>
<bean id="ftpSessionFactory" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.file.remote.session.SessionFactory" />
</bean>
<beans profile="realSSH">
<bean id="ftpSessionFactory"
class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
<property name="host" value="localhost"/>
<property name="user" value="ftptest"/>
<property name="password" value="ftptest"/>
</bean>
</beans>
</beans>

View File

@@ -0,0 +1,255 @@
/*
* 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.sftp.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.sftp.session.SftpFileInfo;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.SftpATTRS;
/**
* Run with -Dspring-profiles-active=realSSH to run with a real SSH server.
*
* Assumes ftptest account on localhost with the following directory tree in the user's root...
*
* <pre class="code">
* $ tree sftpSource/
* sftpSource/
* ├── sftpSource1.txt
* ├── sftpSource2.txt
* └── subSftpSource
* └── subSftpSource1.txt
* </pre>
*
* @author Artem Bilan
* @author Gary Russell
* @since 3.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class SftpServerOutboundTests {
@Autowired
private PollableChannel output;
@Autowired
private DirectChannel inboundGet;
@Autowired
private DirectChannel invalidDirExpression;
@Autowired
private DirectChannel inboundMGet;
@Autowired
private DirectChannel inboundMGetRecursive;
@Autowired
private DirectChannel inboundMGetRecursiveFiltered;
@Autowired
private SessionFactory<SftpFileInfo> sessionFactory;
@Before
public void setup() throws Exception {
purge();
setUpMocksIfNeeded();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void setUpMocksIfNeeded() throws IOException {
if (sessionFactory.toString().startsWith("Mock for")) {
Session session = mock(Session.class);
when(sessionFactory.getSession()).thenReturn(session);
LsEntry entry1 = mock(LsEntry.class);
SftpATTRS attrs1 = mock(SftpATTRS.class);
when(entry1.getAttrs()).thenReturn(attrs1);
when(entry1.getFilename()).thenReturn("sftpSource1.txt");
LsEntry entry2 = mock(LsEntry.class);
SftpATTRS attrs2 = mock(SftpATTRS.class);
when(entry2.getAttrs()).thenReturn(attrs2);
when(entry2.getFilename()).thenReturn("sftpSource2.txt");
LsEntry entry3 = mock(LsEntry.class);
when(entry3.getFilename()).thenReturn("subSftpSource");
SftpATTRS attrs3 = mock(SftpATTRS.class);
when(entry3.getAttrs()).thenReturn(attrs3);
when(attrs3.isDir()).thenReturn(true);
LsEntry entry4 = mock(LsEntry.class);
SftpATTRS attrs4 = mock(SftpATTRS.class);
when(entry4.getAttrs()).thenReturn(attrs4);
// recursion uses a DFA to update the filename to include the subdirectory
new DirectFieldAccessor(entry4).setPropertyValue("filename", "subSftpSource1.txt");
when(entry4.getFilename()).thenCallRealMethod();
when(session.list("sftpSource/sftpSource1.txt")).thenReturn(new LsEntry[] {
entry1
});
when(session.list("sftpSource/")).thenReturn(new LsEntry[] {
entry1, entry2, entry3
});
when(session.list("sftpSource/subSftpSource/")).thenReturn(new LsEntry[] {
entry4
});
when(session.list("sftpSource/subSftpSource/subSftpSource1.txt")).thenReturn(new LsEntry[] {
entry4
});
}
}
@After
public void purge() {
File local = new File("/tmp/sftpOutboundTests/");
purge(local);
local.delete();
}
private void purge(File local) {
File[] files = local.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
this.purge(file);
}
file.delete();
}
}
}
@Test
public void testInt2866LocalDirectoryExpressionGET() {
String dir = "sftpSource/";
this.inboundGet.send(new GenericMessage<Object>(dir + "sftpSource1.txt"));
Message<?> result = this.output.receive(1000);
assertNotNull(result);
File localFile = (File) result.getPayload();
assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir.toUpperCase()));
dir = "sftpSource/subSftpSource/";
this.inboundGet.send(new GenericMessage<Object>(dir + "subSftpSource1.txt"));
result = this.output.receive(1000);
assertNotNull(result);
localFile = (File) result.getPayload();
assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir.toUpperCase()));
}
@Test
public void testInt2866InvalidLocalDirectoryExpression() {
try {
this.invalidDirExpression.send(new GenericMessage<Object>("sftpSource/sftpSource1.txt"));
fail("Exception expected.");
}
catch (Exception e) {
Throwable cause = e.getCause();
assertThat(cause, Matchers.instanceOf(IllegalArgumentException.class));
assertThat(cause.getMessage(), Matchers.startsWith("Failed to make local directory"));
}
}
@Test
@SuppressWarnings("unchecked")
public void testInt2866LocalDirectoryExpressionMGET() {
String dir = "sftpSource/";
this.inboundMGet.send(new GenericMessage<Object>(dir + "*.txt"));
Message<?> result = this.output.receive(1000);
assertNotNull(result);
List<File> localFiles = (List<File>) result.getPayload();
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir));
}
dir = "sftpSource/subSftpSource/";
this.inboundMGet.send(new GenericMessage<Object>(dir + "*.txt"));
result = this.output.receive(1000);
assertNotNull(result);
localFiles = (List<File>) result.getPayload();
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir));
}
}
@Test
@SuppressWarnings("unchecked")
public void testInt3172LocalDirectoryExpressionMGETRecursive() {
String dir = "sftpSource/";
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
Message<?> result = this.output.receive(1000);
assertNotNull(result);
List<File> localFiles = (List<File>) result.getPayload();
assertEquals(3, localFiles.size());
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir));
}
assertThat(localFiles.get(2).getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir + "subSftpSource"));
}
@Test
@SuppressWarnings("unchecked")
public void testInt3172LocalDirectoryExpressionMGETRecursiveFiltered() {
String dir = "sftpSource/";
this.inboundMGetRecursiveFiltered.send(new GenericMessage<Object>(dir + "*"));
Message<?> result = this.output.receive(1000);
assertNotNull(result);
List<File> localFiles = (List<File>) result.getPayload();
// should have filtered sftpSource2.txt
assertEquals(2, localFiles.size());
for (File file : localFiles) {
assertThat(file.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir));
}
assertThat(localFiles.get(1).getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
Matchers.containsString(dir + "subSftpSource"));
}
}

View File

@@ -25,9 +25,10 @@
<constructor-arg name="connectionFactory" ref="redisConnectionFactory"/>
</bean>
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
<property name="port" value="#{T(org.springframework.integration.redis.rules.RedisAvailableRule).REDIS_PORT}"/>
</bean>
</beans>

View File

@@ -351,6 +351,7 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
<listitem>-f - do not sort the list</listitem>
<listitem>-dirs - include directories (excluded by default)</listitem>
<listitem>-links - include symbolic links (excluded by default)</listitem>
<listitem>-R - list the remote directory recursively</listitem>
</itemizedlist>
</para>
<para>
@@ -366,6 +367,13 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
The remote directory that the <emphasis>ls</emphasis> command acted on is provided
in the <classname>file_remoteDirectory</classname> header.
</para>
<para>
When using the recursive option (<code>-R</code>), the <code>fileName</code> includes any subdirectory
elements, representing a relative path to the file (relative to the remote directory). If the <code>-dirs</code>
option is included, each recursive directory is also returned as an element in the list. In this case,
it is recommended that the <code>-1</code> is not used because you would not be able to determine files Vs.
directories, which is achievable using the <code>FileInfo</code> objects.
</para>
<para><emphasis role="bold">get</emphasis></para>
<para>
<emphasis>get</emphasis> retrieves a remote file and supports the following option:
@@ -399,6 +407,27 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
for the filenames is
provided in the <classname>file_remoteFile</classname> header.
</para>
<note>
<title>Notes for when using recursion (<code>-R</code>)</title>
<para>
The pattern is ignored, and <code>*</code> is assumed. By
default, the entire remote tree is retrieved. However, files in the tree can be filtered, by providing a
<classname>FileListFilter</classname>; directories in the tree can also be filtered this way.
A <classname>FileListFilter</classname> can be provided by reference or by <code>filename-pattern</code>
or <code>filename-regex</code> attributes. For example,
<code>filename-regex="(subDir|.*1.txt)"</code> will retrieve all files ending with <code>1.txt</code> in the
remote directory and the subdirectory <code>subDir</code>. If a subdirectory is filtered, no additional
traversal of that subdirectory is performed.
</para>
<para>
The <code>-dirs</code> option is not allowed (the recursive mget uses the recursive <code>ls</code> to
obtain the directory tree and the directories themselves cannot be included in the list).
</para>
<para>
Typically, you would use the <code>#remoteDirectory</code> variable in the <code>local-directory-expression</code>
so that the remote directory structure is retained locally.
</para>
</note>
<para><emphasis role="bold">rm</emphasis></para>
<para>
The <emphasis>rm</emphasis> command has no options.
@@ -433,7 +462,16 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
defines a SpEL expression to generate the name of local file(s) during the transfer.
The root object of the evaluation context is the request Message but, in addition, the <code>remoteFileName</code>
variable is also available, which is particularly useful for <emphasis>mget</emphasis>, for
example: <code>local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo"</code>
example: <code>local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo"</code>.
</para>
<para>
The <emphasis>get</emphasis> and <emphasis>mget</emphasis> commands support
the <emphasis>local-directory-expression</emphasis> attribute. It
defines a SpEL expression to generate the name of local directory(ies) during the transfer.
The root object of the evaluation context is the request Message but, in addition, the <code>remoteDirectory</code>
variable is also available, which is particularly useful for <emphasis>mget</emphasis>, for
example: <code>local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.foo"</code>.
This attribute is mutually exclusive with <emphasis>local-directory</emphasis> attribute.
</para>
<para>
For all commands, the PATH that the command acts on is provided by the 'expression'

View File

@@ -116,9 +116,10 @@ public interface Cafe {
<programlisting language="xml"><![CDATA[<int:gateway id="myGateway" service-interface="org.foo.bar.TestGateway"
default-request-channel="inputC">
<int:default-header name="calledMethod" expression="#gatewayMethod.name"/>
<int:method name="echo" request-channel="inputA" reply-timeout="2" request-timeout="200"/>
<int:method name="echoUpperCase" request-channel="inputB"/>
<int:method name="echoViaDefault"/>
<int:method name="echoUpperCase" request-channel="inputB"/>
<int:method name="echoViaDefault"/>
</int:gateway>]]></programlisting>
<para>
@@ -145,6 +146,45 @@ public interface Cafe {
In the above case you can clearly see how a different value will be set for the 'RESPONSE_TYPE'
header based on the gateway's method.
</para>
<para><emphasis role="bold">Expressions and "Global" Headers</emphasis></para>
<para>
The <code>&lt;header/&gt;</code> element supports <code>expression</code> as an alternative to
<code>value</code>. The SpEL expression is evaluated to determine the value of the header. There is no
<code>#root</code> object but the following variables are available:
<itemizedlist>
<listitem>
#args - an <code>Object[]</code> containing the method arguments
</listitem>
<listitem>
#gatewayMethod - the <classname>java.reflect.Method</classname> object representing the method in the
<code>service-interface</code> that was invoked. A header containing this variable can be used
later in the flow, for example, for routing. For example, if you wish to route on the simple method
name, you might add a header, with expression <code>#gatewayMethod.name</code>.
<note>
The <classname>java.reflect.Method</classname> is not serializable; a header with expression
<code>#gatewayMethod</code> will be lost if you later serialize the message. So, you may wish
to use <code>#gatewayMethod.name</code> or <code>#gatewayMethod.toString()</code> in those cases;
the <code>toString()</code> method provides a String representation of the method, including
parameter and return types.
</note>
<note>
Prior to 3.0, the <code>#method</code> variable was available, representing the method name only.
This is still available, but deprecated; use <code>#gatewayMethod.name</code> instead.
</note>
</listitem>
</itemizedlist>
</para>
<para>
Since 3.0, <code>&lt;default-header/&gt;</code>s can be defined to add headers to all messages produced
by the gateway, regardless of the method invoked. Specific headers defined for a method take precedence
over default headers. Specific headers defined for a method here will override any <code>@Header</code> annotations
in the service interface. However, default headers will NOT override any <code>@Header</code> annotations
in the service interface.
</para>
<para>
The gateway now also supports a <code>default-payload-expression</code> which will be applied for all methods
(unless overridden).
</para>
</section>
<section id="gateway-calling-no-argument-methods">

View File

@@ -387,6 +387,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
<listitem>-f - do not sort the list</listitem>
<listitem>-dirs - include directories (excluded by default)</listitem>
<listitem>-links - include symbolic links (excluded by default)</listitem>
<listitem>-R - list the remote directory recursively</listitem>
</itemizedlist>
</para>
<para>
@@ -402,6 +403,13 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
The remote directory that the <emphasis>ls</emphasis> command acted on is provided
in the <classname>file_remoteDirectory</classname> header.
</para>
<para>
When using the recursive option (<code>-R</code>), the <code>fileName</code> includes any subdirectory
elements, representing a relative path to the file (relative to the remote directory). If the <code>-dirs</code>
option is included, each recursive directory is also returned as an element in the list. In this case,
it is recommended that the <code>-1</code> is not used because you would not be able to determine files Vs.
directories, which is achievable using the <code>FileInfo</code> objects.
</para>
<para><emphasis role="bold">get</emphasis></para>
<para>
<emphasis>get</emphasis> retrieves a remote file and supports the following option:
@@ -435,6 +443,27 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
for the filenames is
provided in the <classname>file_remoteFile</classname> header.
</para>
<note>
<title>Notes for when using recursion (<code>-R</code>)</title>
<para>
The pattern is ignored, and <code>*</code> is assumed. By
default, the entire remote tree is retrieved. However, files in the tree can be filtered, by providing a
<classname>FileListFilter</classname>; directories in the tree can also be filtered this way.
A <classname>FileListFilter</classname> can be provided by reference or by <code>filename-pattern</code>
or <code>filename-regex</code> attributes. For example,
<code>filename-regex="(subDir|.*1.txt)"</code> will retrieve all files ending with <code>1.txt</code> in the
remote directory and the subdirectory <code>subDir</code>. If a subdirectory is filtered, no additional
traversal of that subdirectory is performed.
</para>
<para>
The <code>-dirs</code> option is not allowed (the recursive mget uses the recursive <code>ls</code> to
obtain the directory tree and the directories themselves cannot be included in the list).
</para>
<para>
Typically, you would use the <code>#remoteDirectory</code> variable in the <code>local-directory-expression</code>
so that the remote directory structure is retained locally.
</para>
</note>
<para><emphasis role="bold">rm</emphasis></para>
<para>
The <emphasis>rm</emphasis> command has no options.
@@ -471,6 +500,15 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
variable is also available, which is particularly useful for <emphasis>mget</emphasis>, for
example: <code>local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo"</code>
</para>
<para>
The <emphasis>get</emphasis> and <emphasis>mget</emphasis> commands support
the <emphasis>local-directory-expression</emphasis> attribute. It
defines a SpEL expression to generate the name of local directory(ies) during the transfer.
The root object of the evaluation context is the request Message but, in addition, the <code>remoteDirectory</code>
variable is also available, which is particularly useful for <emphasis>mget</emphasis>, for
example: <code>local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.foo"</code>.
This attribute is mutually exclusive with <emphasis>local-directory</emphasis> attribute.
</para>
<para>
For all commands, the PATH that the command acts on is provided by the 'expression'
property of the gateway. For the mget command, the expression might evaluate to '*', meaning

View File

@@ -155,6 +155,20 @@
<section id="3.0-general">
<title>General Changes</title>
<section id="3.0-gateway">
<title>&lt;gateway&gt; Changes</title>
<para>
<itemizedlist>
<listitem>
It is now possible to set common headers across all gateway methods, and more options
are provided for adding, to the message, information about which method was invoked.
</listitem>
</itemizedlist>
</para>
<para>
For more information see <xref linkend="gateway"/>.
</para>
</section>
<section id="3.0-corr-endpoint-empty-groups">
<title>Aggregator 'empty-group-min-timeout' property</title>
<para><classname>AbstractCorrelatingMessageHandler</classname> provides a new property
@@ -238,14 +252,28 @@
<section id="3.0-xFTP-gw">
<title>FTP, SFTP and FTPS Gateways</title>
<para>
The gateways now support the <code>mv</code> command, enabling the renaming of remote
files.
<itemizedlist>
<listitem>
The gateways now support the <code>mv</code> command, enabling the renaming of remote
files.
</listitem>
<listitem>
The gateways now support recursive <code>ls</code> and <code>mget</code> commands, enabling
the retrieval of a remote file tree.
</listitem>
<listitem>
The <code>local-filename-generator-expression</code> attribute is now supported,
enabling the naming of local files during transfer. By default, the same
name as the remote file is used.
</listitem>
<listitem>
The <code>local-directory-expression</code> attribute is now supported,
enabling the naming of local directories during transfer based on the remote directory.
</listitem>
</itemizedlist>
</para>
<para>
The <code>local-filename-generator-expression</code> attribute is now supported,
enabling the naming of local files during transfer. By default, the same
name as the remote file is used. For more information, see
<xref linkend="ftp-outbound-gateway"/> and <xref linkend="sftp-outbound-gateway"/>.
For more information, see <xref linkend="ftp-outbound-gateway"/> and <xref linkend="sftp-outbound-gateway"/>.
</para>
</section>
<section id="3.0-jdbc-mysql-v5_6_4">