diff --git a/docs/src/reference/docbook/ftp.xml b/docs/src/reference/docbook/ftp.xml
index fbe16e72dd..7bb5b8e6c1 100644
--- a/docs/src/reference/docbook/ftp.xml
+++ b/docs/src/reference/docbook/ftp.xml
@@ -346,17 +346,34 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp
FTP Session Caching
- One of the optimizations implemented by the FTP adapters is session caching. Similar to JDBC pooling of Connections, the FTP Adapters maintain a
- pool of Sessions by default. However there are times when this behavior is not desired (e.g., security etc.).
- To disable session caching you can set the cache-sessions attribute to false (the default value is true).
-]]>
+ As of version 2.1 we've exposed more flexibility with regard to session management for remote file adapters (e.g., FTP, SFTP etc).
+ In previous versions the sessions were cached automatically by default. We did expose a cache-sessions attribute for
+ disabling the auto caching, but that solution did not provide a way to configure other session caching attributes. For example, one
+ of the requested features was to support a limit on the number of sessions created since a remote server may impose a limit on the
+ number of client connections. To support that requirement and other configuration options, we decided to promote explicit definition
+ of the CachingSessionFactory instance. That provides the sessionCacheSize and sessionWaitTimeout
+ properties. As its name suggests, the sessionCacheSize property controls how many active sessions this adapter will
+ maintain in its cache (the DEFAULT is unbounded). If the sessionCacheSize threshold has been reached, any attempt to
+ acquire another session will block until either one of the cached sessions becomes available or until the wait time for a Session
+ expires (the DEFAULT wait time is Integer.MAX_VALUE). The sessionWaitTimeout property enables configuration of that value.
+
+
+ If you want your Sessions to be cached, simply configure your default Session Factory as described above and then
+ wrap it in an instance of CachingSessionFactory where you may provide those additional properties.
+
+
+
+
+
+
+
+
+
+ ]]>
+
+ In the above example you see a CachingSessionFactory created with the
+ sessionCacheSize set to 10 and the sessionWaitTimeout set to 1 second (its value is in millliseconds).
-The same attribute can also be used with Outbound Channel Adapters.
diff --git a/docs/src/reference/docbook/sftp.xml b/docs/src/reference/docbook/sftp.xml
index 2c4505fca0..0a51e5f7ea 100644
--- a/docs/src/reference/docbook/sftp.xml
+++ b/docs/src/reference/docbook/sftp.xml
@@ -289,18 +289,35 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
SFTP Session Caching
-
- One of the optimizations implemented by the SFTP adapters is session caching. Similar to JDBC pooling of Connections, the SFTP Adapters maintain a
- pool of Sessions by default. However there are times when this behavior is not desired (e.g., security etc.).
- To disable session caching you can set the cache-sessions attribute to false (the default value is true).
-]]>
+
+ As of version 2.1 we've exposed more flexibility with regard to session management for remote file adapters (e.g., FTP, SFTP etc).
+ In previous versions the sessions were cached automatically by default. We did expose a cache-sessions attribute for
+ disabling the auto caching, but that solution did not provide a way to configure other session caching attributes. For example, one
+ of the requested features was to support a limit on the number of sessions created since a remote server may impose a limit on the
+ number of client connections. To support that requirement and other configuration options, we decided to promote explicit definition
+ of the CachingSessionFactory instance. That provides the sessionCacheSize and sessionWaitTimeout
+ properties. As its name suggests, the sessionCacheSize property controls how many active sessions this adapter will
+ maintain in its cache (the DEFAULT is unbounded). If the sessionCacheSize threshold has been reached, any attempt to
+ acquire another session will block until either one of the cached sessions becomes available or until the wait time for a Session
+ expires (the DEFAULT wait time is Integer.MAX_VALUE). The sessionWaitTimeout property enables configuration of that value.
+
+
+ If you want your Sessions to be cached, simply configure your default Session Factory as described above and then
+ wrap it in an instance of CachingSessionFactory where you may provide those additional properties.
+
+
+
+
+
+
+
+
+
+ ]]>
+
+ In the above example you see a CachingSessionFactory created with the
+ sessionCacheSize set to 10 and the sessionWaitTimeout set to 1 second (its value is in millliseconds).
-The same attribute can also be used with Outbound Channel Adapters.
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java
index 5a7f25108e..4b3c559696 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileInboundChannelAdapterParser.java
@@ -18,12 +18,17 @@ package org.springframework.integration.file.config;
import org.w3c.dom.Element;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import org.springframework.beans.BeanMetadataElement;
+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;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.util.StringUtils;
/**
@@ -35,34 +40,41 @@ import org.springframework.util.StringUtils;
*/
public abstract class AbstractRemoteFileInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
+ private final Log logger = LogFactory.getLog(this.getClass());
+
@Override
protected final BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder synchronizerBuilder = BeanDefinitionBuilder.genericBeanDefinition(
this.getInboundFileSynchronizerClassname());
- // build the SessionFactory and provide as a constructor argument
- String cacheSessions = element.getAttribute("cache-sessions");
- if ("false".equalsIgnoreCase(cacheSessions)) {
- synchronizerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
+ // This whole block must be refactored once the cache-session attribute is removed
+ String sessionFactoryName = element.getAttribute("session-factory");
+ BeanDefinition sessionFactoryDefinition = parserContext.getReaderContext().getRegistry().getBeanDefinition(sessionFactoryName);
+ String sessionFactoryClassName = sessionFactoryDefinition.getBeanClassName();
+ if (StringUtils.hasText(sessionFactoryClassName) && sessionFactoryClassName.endsWith(CachingSessionFactory.class.getName())) {
+ synchronizerBuilder.addConstructorArgValue(sessionFactoryDefinition);
}
else {
- BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- "org.springframework.integration.file.remote.session.CachingSessionFactory");
- sessionFactoryBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
- synchronizerBuilder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
+ String cacheSessions = element.getAttribute("cache-sessions");
+ if (StringUtils.hasText(cacheSessions) && logger.isWarnEnabled()) {
+ logger.warn("The 'cache-sessions' attribute is deprecated as of version 2.1. " +
+ "Please configure a CachingSessionFactory explicitly instead.");
+ }
+ if ("false".equalsIgnoreCase(cacheSessions)) {
+ synchronizerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
+ }
+ else {
+ BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(CachingSessionFactory.class);
+ sessionFactoryBuilder.addConstructorArgReference(sessionFactoryName);
+ synchronizerBuilder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
+ }
}
+ // end of what needs to be refactored once cache-session is removed
// configure the InboundFileSynchronizer properties
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "remote-directory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "delete-remote-files");
- String localFileGeneratorExpression = element.getAttribute("local-filename-generator-expression");
-
- if (StringUtils.hasText(localFileGeneratorExpression)){
- BeanDefinitionBuilder localFileGeneratorExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class);
- localFileGeneratorExpressionBuilder.addConstructorArgValue(localFileGeneratorExpression);
- synchronizerBuilder.addPropertyValue("localFilenameGeneratorExpression", localFileGeneratorExpressionBuilder.getBeanDefinition());
- }
-
+
String remoteFileSeparator = element.getAttribute("remote-file-separator");
synchronizerBuilder.addPropertyValue("remoteFileSeparator", remoteFileSeparator);
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "temporary-file-suffix");
@@ -72,11 +84,17 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
BeanDefinitionBuilder messageSourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(this.getMessageSourceClassname());
messageSourceBuilder.addConstructorArgValue(synchronizerBuilder.getBeanDefinition());
String comparator = element.getAttribute("comparator");
- if (StringUtils.hasText(comparator)){
+ if (StringUtils.hasText(comparator)) {
messageSourceBuilder.addConstructorArgReference(comparator);
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(messageSourceBuilder, element, "local-directory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(messageSourceBuilder, element, "auto-create-local-directory");
+ String localFileGeneratorExpression = element.getAttribute("local-filename-generator-expression");
+ if (StringUtils.hasText(localFileGeneratorExpression)) {
+ BeanDefinitionBuilder localFileGeneratorExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class);
+ localFileGeneratorExpressionBuilder.addConstructorArgValue(localFileGeneratorExpression);
+ synchronizerBuilder.addPropertyValue("localFilenameGeneratorExpression", localFileGeneratorExpressionBuilder.getBeanDefinition());
+ }
return messageSourceBuilder.getBeanDefinition();
}
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java
index d380f4d52a..f14c43b32f 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java
@@ -15,20 +15,25 @@
*/
package org.springframework.integration.file.config;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* @author Gary Russell
+ * @author Oleg Zhurakousky
* @since 2.1
- *
*/
-public abstract class AbstractRemoteFileOutboundGatewayParser extends
- AbstractConsumerEndpointParser {
+public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractConsumerEndpointParser {
+
+ private final Log logger = LogFactory.getLog(this.getClass());
@Override
protected String getInputChannelAttributeName() {
@@ -38,17 +43,33 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(getGatewayClassName());
- // build the SessionFactory and provide as a constructor argument
- String cacheSessions = element.getAttribute("cache-sessions");
- if ("false".equalsIgnoreCase(cacheSessions)) {
- builder.addConstructorArgReference(element.getAttribute("session-factory"));
+
+ // build the SessionFactory and provide it as a constructor argument
+
+ // This whole block must be refactored once cache-session attribute is removed
+ String sessionFactoryName = element.getAttribute("session-factory");
+ BeanDefinition sessionFactoryDefinition = parserContext.getReaderContext().getRegistry().getBeanDefinition(sessionFactoryName);
+ String sessionFactoryClassName = sessionFactoryDefinition.getBeanClassName();
+ if (StringUtils.hasText(sessionFactoryClassName) && sessionFactoryClassName.endsWith(CachingSessionFactory.class.getName())) {
+ builder.addConstructorArgValue(sessionFactoryDefinition);
}
else {
- BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- "org.springframework.integration.file.remote.session.CachingSessionFactory");
- sessionFactoryBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
- builder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
+ String cacheSessions = element.getAttribute("cache-sessions");
+ if (StringUtils.hasText(cacheSessions) && logger.isWarnEnabled()) {
+ logger.warn("The 'cache-sessions' attribute is deprecated as of version 2.1." +
+ "Please configure a CachingSessionFactory explicitly instead.");
+ }
+ if ("false".equalsIgnoreCase(cacheSessions)) {
+ builder.addConstructorArgReference(element.getAttribute("session-factory"));
+ }
+ else {
+ BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(CachingSessionFactory.class);
+ sessionFactoryBuilder.addConstructorArgReference(sessionFactoryName);
+ builder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
+ }
}
+ // end of what needs to be refactored once cache-session is removed
+
builder.addConstructorArgValue(element.getAttribute("command"));
builder.addConstructorArgValue(element.getAttribute(EXPRESSION_ATTRIBUTE));
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "command-options", "options");
@@ -75,14 +96,17 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends
if (count > 1) {
parserContext.getReaderContext().error("at most one of 'filename-pattern', " +
"'filename-regex', or 'filter' is allowed on remote file inbound adapter", element);
- } else if (hasFilter) {
+ }
+ else if (hasFilter) {
builder.addPropertyReference("filter", filter);
- } else if (hasFileNamePattern) {
+ }
+ else if (hasFileNamePattern) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
this.getSimplePatternFileListFilterClassname());
filterBuilder.addConstructorArgValue(fileNamePattern);
builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
- } else if (hasFileNameRegex) {
+ }
+ else if (hasFileNameRegex) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
this.getRegexPatternFileListFilterClassname());
filterBuilder.addConstructorArgValue(fileNameRegex);
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java
index 4a94afc433..7a3260fa9d 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@ package org.springframework.integration.file.config;
import org.w3c.dom.Element;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -26,6 +28,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
+import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.util.StringUtils;
/**
@@ -34,23 +37,34 @@ import org.springframework.util.StringUtils;
* @since 2.0
*/
public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
-
+ private final Log logger = LogFactory.getLog(this.getClass());
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.file.remote.handler.FileTransferringMessageHandler");
- // build the SessionFactory and provide as a constructor argument
- String cacheSessions = element.getAttribute("cache-sessions");
- if ("false".equalsIgnoreCase(cacheSessions)) {
- handlerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
+ // This whole block must be refactored once cache-session attribute is removed
+ String sessionFactoryName = element.getAttribute("session-factory");
+ BeanDefinition sessionFactoryDefinition = parserContext.getReaderContext().getRegistry().getBeanDefinition(sessionFactoryName);
+ String sessionFactoryClassName = sessionFactoryDefinition.getBeanClassName();
+ if (StringUtils.hasText(sessionFactoryClassName) && sessionFactoryClassName.endsWith(CachingSessionFactory.class.getName())){
+ handlerBuilder.addConstructorArgValue(sessionFactoryDefinition);
}
else {
- BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(
- "org.springframework.integration.file.remote.session.CachingSessionFactory");
- sessionFactoryBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
- handlerBuilder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
+ String cacheSessions = element.getAttribute("cache-sessions");
+ if (StringUtils.hasText(cacheSessions)){
+ logger.warn("The 'cache-sessions' attribute is deprecated since v2.1. Consider configuring CachingSessionFactory explicitly");
+ }
+ if ("false".equalsIgnoreCase(cacheSessions)) {
+ handlerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
+ }
+ else {
+ BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(CachingSessionFactory.class);
+ sessionFactoryBuilder.addConstructorArgReference(sessionFactoryName);
+ handlerBuilder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
+ }
}
+ // end of what needs to be refactored once cache-session is removed
// configure MessageHandler properties
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "temporary-file-suffix");
diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java
index bb67b4efaa..828c498938 100644
--- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java
+++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2011 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,13 +19,12 @@ package org.springframework.integration.file.remote.session;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.Queue;
-import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.beans.factory.DisposableBean;
+import org.springframework.integration.util.UpperBound;
/**
* A {@link SessionFactory} implementation that caches Sessions for reuse without
@@ -41,41 +40,41 @@ public class CachingSessionFactory implements SessionFactory, DisposableBean {
private static final Log logger = LogFactory.getLog(CachingSessionFactory.class);
- public static final int DEFAULT_POOL_SIZE = 10;
+ private volatile long sessionWaitTimeout = Integer.MAX_VALUE;
- private final Queue queue;
+ private final LinkedBlockingQueue queue = new LinkedBlockingQueue();
private final SessionFactory sessionFactory;
- private final int maxPoolSize;
+ private final UpperBound sessionPermits;
public CachingSessionFactory(SessionFactory sessionFactory) {
- this(sessionFactory, DEFAULT_POOL_SIZE);
+ this(sessionFactory, 0);
}
-
- public CachingSessionFactory(SessionFactory sessionFactory, int maxPoolSize) {
+
+ public CachingSessionFactory(SessionFactory sessionFactory, int sessionCacheSize) {
this.sessionFactory = sessionFactory;
- this.maxPoolSize = maxPoolSize;
- this.queue = new ArrayBlockingQueue(this.maxPoolSize, true);
+ this.sessionPermits = new UpperBound(sessionCacheSize);
}
- public Session getSession() {
- Session session = this.queue.poll();
- if (session == null || !session.isOpen()) {
- if (session != null && logger.isTraceEnabled()) {
- logger.trace("Located session in the pool but it is stale, will create new one.");
- }
- session = this.sessionFactory.getSession();
- if (logger.isTraceEnabled()) {
- logger.trace("Created new session");
- }
- }
- else if (logger.isTraceEnabled()) {
- logger.trace("Using session from the pool");
+ /**
+ * Sets the limit of how long to wait for a session to become available.
+ *
+ * @throws {@link IllegalStateException} if the wait expires prior to a Session becoming available.
+ */
+ public void setSessionWaitTimeout(long sessionWaitTimeout) {
+ this.sessionWaitTimeout = sessionWaitTimeout;
+ }
+
+ public Session getSession() {
+ boolean permitted = this.sessionPermits.tryAcquire(this.sessionWaitTimeout);
+ if (!permitted) {
+ throw new IllegalStateException("Timed out while waiting to aquire a Session.");
}
+ Session session = this.doGetSession();
return new CachedSession(session);
}
@@ -87,11 +86,25 @@ public class CachingSessionFactory implements SessionFactory, DisposableBean {
}
}
+ private Session doGetSession() {
+ Session session = this.queue.poll();
+ if (session != null && !session.isOpen()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Received a stale Session, will attempt to get a new one.");
+ }
+ return this.doGetSession();
+ }
+ else if (session == null){
+ session = this.sessionFactory.getSession();
+ }
+ return session;
+ }
+
private void closeSession(Session session) {
try {
if (session != null) {
session.close();
- }
+ }
}
catch (Throwable e) {
if (logger.isWarnEnabled()) {
@@ -111,18 +124,11 @@ public class CachingSessionFactory implements SessionFactory, DisposableBean {
}
public void close() {
- if (queue.size() < maxPoolSize) {
- if (logger.isTraceEnabled()) {
- logger.trace("Releasing target session back to the pool");
- }
- queue.add(targetSession);
- }
- else {
- if (logger.isTraceEnabled()) {
- logger.trace("Disconnecting target session");
- }
- targetSession.close();
+ if (logger.isDebugEnabled()){
+ logger.debug("Releasing Session back to the pool.");
}
+ queue.add(targetSession);
+ sessionPermits.release();
}
public boolean remove(String path) throws IOException{
diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd
index 73f0b230e4..9fcec8088b 100644
--- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd
+++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd
@@ -233,7 +233,7 @@ endpoint itself is a Polling Consumer for a channel with a queue.
@@ -384,7 +384,7 @@ endpoint itself is a Polling Consumer for a channel with a queue.
diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml
index 54e292e0c9..19250d1901 100644
--- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml
+++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml
@@ -15,6 +15,12 @@
+
+
+
+
+
+
diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/SessionFactoryTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/SessionFactoryTests.java
index 2de9f0ede5..e8911cfddb 100644
--- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/SessionFactoryTests.java
+++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/SessionFactoryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2011 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,25 @@
package org.springframework.integration.ftp.session;
import java.lang.reflect.Field;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.net.ftp.FTPClient;
+import org.junit.Ignore;
import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.integration.file.remote.session.CachingSessionFactory;
+import org.springframework.integration.file.remote.session.Session;
+import org.springframework.integration.file.remote.session.SessionFactory;
+import org.springframework.integration.test.util.TestUtils;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertEquals;
+
/**
* @author Oleg Zhurakousky
*
@@ -49,4 +62,87 @@ public class SessionFactoryTests {
}
}
}
+
+ @Test
+ public void testStaleConnection() throws Exception{
+ SessionFactory sessionFactory = Mockito.mock(SessionFactory.class);
+ Session sessionA = Mockito.mock(Session.class);
+ Session sessionB = Mockito.mock(Session.class);
+ Mockito.when(sessionA.isOpen()).thenReturn(true);
+ Mockito.when(sessionB.isOpen()).thenReturn(false);
+
+ Mockito.when(sessionFactory.getSession()).thenReturn(sessionA);
+ Mockito.when(sessionFactory.getSession()).thenReturn(sessionB);
+
+ CachingSessionFactory cachingFactory = new CachingSessionFactory(sessionFactory, 2);
+
+ Session firstSession = cachingFactory.getSession();
+ Session secondSession = cachingFactory.getSession();
+ secondSession.close();
+ Session nonStaleSession = cachingFactory.getSession();
+ assertEquals(TestUtils.getPropertyValue(firstSession, "targetSession"), TestUtils.getPropertyValue(nonStaleSession, "targetSession"));
+ }
+
+ @Test
+ public void testSameSessionFromThePool() throws Exception{
+ SessionFactory sessionFactory = Mockito.mock(SessionFactory.class);
+ Session session = Mockito.mock(Session.class);
+ Mockito.when(sessionFactory.getSession()).thenReturn(session);
+
+ CachingSessionFactory cachingFactory = new CachingSessionFactory(sessionFactory, 2);
+
+ Session s1 = cachingFactory.getSession();
+ s1.close();
+ Session s2 = cachingFactory.getSession();
+ s2.close();
+ assertEquals(TestUtils.getPropertyValue(s1, "targetSession"), TestUtils.getPropertyValue(s2, "targetSession"));
+ Mockito.verify(sessionFactory, Mockito.times(2)).getSession();
+ }
+
+ @Test (expected=IllegalStateException.class) // timeout expire
+ public void testSessionWaitExpire() throws Exception{
+ SessionFactory sessionFactory = Mockito.mock(SessionFactory.class);
+ Session session = Mockito.mock(Session.class);
+ Mockito.when(sessionFactory.getSession()).thenReturn(session);
+
+ CachingSessionFactory cachingFactory = new CachingSessionFactory(sessionFactory, 2);
+
+ cachingFactory.setSessionWaitTimeout(3000);
+
+ cachingFactory.getSession();
+ cachingFactory.getSession();
+ cachingFactory.getSession();
+ }
+
+ @Test
+ @Ignore
+ public void testConnectionLimit() throws Exception{
+ ExecutorService executor = Executors.newCachedThreadPool();
+ DefaultFtpSessionFactory sessionFactory = new DefaultFtpSessionFactory();
+ sessionFactory.setHost("192.168.28.143");
+ sessionFactory.setPassword("password");
+ sessionFactory.setUsername("user");
+ final CachingSessionFactory factory = new CachingSessionFactory(sessionFactory, 2);
+
+ final Random random = new Random();
+ final AtomicInteger failures = new AtomicInteger();
+ for (int i = 0; i < 30; i++) {
+ executor.execute(new Runnable() {
+ public void run() {
+ try {
+ Session session = factory.getSession();
+ Thread.sleep(random.nextInt(5000));
+ session.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ failures.incrementAndGet();
+ }
+ }
+ });
+ }
+ executor.shutdown();
+ executor.awaitTermination(10000, TimeUnit.SECONDS);
+
+ assertEquals(0, failures.get());
+ }
}
diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd
index 4b69190db9..85da6c40d8 100644
--- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd
+++ b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.1.xsd
@@ -37,7 +37,7 @@
@@ -168,7 +168,7 @@ endpoint itself is a Polling Consumer for a channel with a queue.
@@ -302,7 +302,7 @@ endpoint itself is a Polling Consumer for a channel with a queue.