Expose session limitation on remote file adapters

  Deprecate 'cache-sessions' attribute

  Add 'sessionCacheSize' and 'sessionWaitTimeout' attributes on CachingSessionFactory

  Update documentation
This commit is contained in:
Oleg Zhurakousky
2011-10-14 14:31:57 -04:00
committed by Mark Fisher
parent 761a797ee1
commit 7f92089584
10 changed files with 292 additions and 97 deletions

View File

@@ -346,17 +346,35 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp
<section id="ftp-session-caching">
<title>FTP Session Caching</title>
<para>
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 <code>cache-sessions</code> attribute to <code>false</code> (the default value is <code>true</code>).
<programlisting language="xml"><![CDATA[<int-ftp:inbound-channel-adapter id="ftpInbound"
channel="ftpChannel"
. . .
cache-sessions="false"
. . .
</int-ftp:inbound-channel-adapter>]]></programlisting>
Since 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. And although we did expose <code>cache-session</code> attribute which would
allow you to turn auto caching off it was still not sufficient when it came to other session caching attributes.
For example; One of the requirement we received was to control session limit if remote server imposes client connection limit.
Now such behavior is controlled by the <classname>CachingSessionFactory</classname> and its <code>sessionCacheSize</code> and
<code>sessionWaitTimeout</code> properties. As its name suggest <code>sessionCacheSize</code> property controls how many active
sessions this adapter will maintain in its cache (DEFAULT unbounded). If <code>sessionCacheSize</code> threshold has been reached any
attempt to get more session will block until session becomes available or until wait time for a session to become available
expires (DEFAULT Integer.MAX_VALUE). The wait time for a session to become available can also be controlled via <code>sessionWaitTimeout</code>
property.
</para>
<para>
Since version 2.1 if you need you session to be cahced you can simply configure your default Session Factory as
described above and than wrap it in the <classname>CachingSessionFactory</classname> while providing additional properties.
<programlisting language="xml"><![CDATA[<bean id="ftpSessionFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
<property name="host" value="localhost"/>
</bean>
<bean id="cachingSessionFactory" class="org.springframework.integration.file.remote.session.CachingSessionFactory">
<constructor-arg ref="ftpSessionFactory"/>
<constructor-arg value="10"/>
<property name="sessionWaitTimeout" value="1000"/>
</bean>]]></programlisting>
In the above example you see <classname>CachingSessionFactory</classname> created with
<code>sessionCacheSize</code> set to 10 with <code>sessionWait</code> timeout set to 1 second.
The same attribute can also be used with Outbound Channel Adapters.
</para>
</section>
</chapter>

View File

@@ -290,17 +290,34 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
<section id="sftp-session-caching">
<title>SFTP Session Caching</title>
<para>
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 <code>cache-sessions</code> attribute to <code>false</code> (the default value is <code>true</code>).
<programlisting language="xml"><![CDATA[<int-sftp:inbound-channel-adapter id="ftpInbound"
channel="sftpChannel"
. . .
cache-sessions="false"
. . .
</int-sftp:inbound-channel-adapter>]]></programlisting>
Since 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. And although we did expose <code>cache-session</code> attribute which would
allow you to turn auto caching off it was still not sufficient when it came to other session caching attributes.
For example; One of the requirement we received was to control session limit if remote server imposes client connection limit.
Now such behavior is controlled by the <classname>CachingSessionFactory</classname> and its <code>sessionCacheSize</code> and
<code>sessionWaitTimeout</code> properties. As its name suggest <code>sessionCacheSize</code> property controls how many active
sessions this adapter will maintain in its cache (DEFAULT unbounded). If <code>sessionCacheSize</code> threshold has been reached any
attempt to get more session will block until session becomes available or until wait time for a session to become available
expires (DEFAULT Integer.MAX_VALUE). The wait time for a session to become available can also be controlled via <code>sessionWaitTimeout</code>
property.
</para>
<para>
Since version 2.1 if you need you session to be cahced you can simply configure your default Session Factory as
described above and than wrap it in the <classname>CachingSessionFactory</classname> while providing additional properties.
<programlisting language="xml"><![CDATA[<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
<property name="host" value="localhost"/>
</bean>
<bean id="cachingSessionFactory" class="org.springframework.integration.file.remote.session.CachingSessionFactory">
<constructor-arg ref="sftpSessionFactory"/>
<property name="sessionCacheSize" value="10"/>
<property name="sessionWaitTimeout" value="1000"/>
</bean>]]></programlisting>
In the above example you see <classname>CachingSessionFactory</classname> created with
<code>sessionCacheSize</code> set to 10 with <code>sessionWait</code> timeout set to 1 second.
The same attribute can also be used with Outbound Channel Adapters.
</para>
</section>
</chapter>

View File

@@ -18,12 +18,16 @@ 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;
/**
@@ -34,23 +38,35 @@ import org.springframework.util.StringUtils;
* @since 2.0
*/
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 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.warn("The 'cache-sessions' attribute is deprecated since v2.1. Consider configuring CachingSessionFactory explicitly");
}
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");

View File

@@ -15,20 +15,27 @@
*/
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 {
private final Log logger = LogFactory.getLog(this.getClass());
@Override
protected String getInputChannelAttributeName() {
@@ -39,16 +46,30 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends
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"));
// 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.warn("The 'cache-sessions' attribute is deprecated since v2.1. Consider configuring CachingSessionFactory explicitly");
}
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");

View File

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

View File

@@ -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
@@ -37,46 +36,47 @@ import org.springframework.beans.factory.DisposableBean;
* @author Mark Fisher
* @since 2.0
*/
public class CachingSessionFactory implements SessionFactory, DisposableBean {
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<Session> queue;
private volatile LinkedBlockingQueue<Session> queue = new LinkedBlockingQueue<Session>();
private final SessionFactory sessionFactory;
private final int maxPoolSize;
private final UpperBound sessionSizeManager;
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<Session>(this.maxPoolSize, true);
this.sessionSizeManager = new UpperBound(sessionCacheSize);
}
/**
* Sets the limit of how long it will wait for a session to become available after which
* it will throw {@link IllegalStateException}.
*/
public void setSessionWaitTimeout(long sessionWaitTimeout) {
this.sessionWaitTimeout = sessionWaitTimeout;
}
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");
}
public Session getSession() {
try {
boolean permitted = this.sessionSizeManager.tryAcquire(this.sessionWaitTimeout);
if (!permitted){
throw new IllegalStateException("Timed out while waiting to aquire Session");
}
Session session = this.doGetSession();
return new CachedSession(session);
} catch (Exception e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Exception was received during attempt to obtain Session", e);
}
else if (logger.isTraceEnabled()) {
logger.trace("Using session from the pool");
}
return new CachedSession(session);
}
public void destroy() {
@@ -86,6 +86,21 @@ public class CachingSessionFactory implements SessionFactory, DisposableBean {
}
}
}
private Session doGetSession() throws InterruptedException {
Session session = this.queue.poll();
if (session != null && !session.isOpen()){
if (logger.isDebugEnabled()) {
logger.debug("Received 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 {
@@ -111,18 +126,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 into the pool");
}
queue.add(targetSession);
sessionSizeManager.release();
}
public boolean remove(String path) throws IOException{
@@ -153,5 +161,4 @@ public class CachingSessionFactory implements SessionFactory, DisposableBean {
this.targetSession.mkdir(directory);
}
}
}

View File

@@ -233,7 +233,7 @@ endpoint itself is a Polling Consumer for a channel with a queue.
<xsd:attribute name="cache-sessions" type="xsd:boolean" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify whether the Sessions should be cached. Default is true.
[DEPRECATED] Consider wrapping you SessionFactory in the org.springframework.integration.file.remote.session.CachingSessionFactory.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -384,7 +384,7 @@ endpoint itself is a Polling Consumer for a channel with a queue.
<xsd:attribute name="cache-sessions" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify whether the Sessions should be cached. Default is true.
[DEPRECATED] Consider wrapping you SessionFactory in the org.springframework.integration.file.remote.session.CachingSessionFactory.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -15,6 +15,12 @@
<property name="clientMode" value="0"/>
<property name="fileType" value="2"/>
</bean>
<bean id="cachingSessionFactory" class="org.springframework.integration.file.remote.session.CachingSessionFactory">
<constructor-arg ref="ftpSessionFactory"/>
<constructor-arg value="10"/>
<property name="sessionWaitTimeout" value="1000"/>
</bean>
<int-ftp:outbound-channel-adapter id="ftpOutbound"
channel="ftpChannel"
@@ -29,7 +35,7 @@
<int-ftp:outbound-channel-adapter id="ftpOutbound2"
channel="ftpChannel"
session-factory="ftpSessionFactory"
session-factory="cachingSessionFactory"
remote-directory="foo/bar"
charset="UTF-8"
remote-file-separator="."
@@ -39,7 +45,7 @@
<int-ftp:outbound-channel-adapter id="simpleAdapter"
channel="ftpChannel"
session-factory="ftpSessionFactory"
session-factory="cachingSessionFactory"
remote-directory="foo/bar"/>
<int:publish-subscribe-channel id="ftpChannel"/>

View File

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

View File

@@ -37,7 +37,7 @@
<xsd:attribute name="cache-sessions" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify whether the Sessions should be cached. Default is true.
[DEPRECATED] Consider wrapping you SessionFactory in the org.springframework.integration.file.remote.session.CachingSessionFactory.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -168,7 +168,7 @@ endpoint itself is a Polling Consumer for a channel with a queue.
<xsd:attribute name="cache-sessions" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify whether the Sessions should be cached. Default is true.
[DEPRECATED] Consider wrapping you SessionFactory in the org.springframework.integration.file.remote.session.CachingSessionFactory.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
@@ -302,7 +302,7 @@ endpoint itself is a Polling Consumer for a channel with a queue.
<xsd:attribute name="cache-sessions" type="xsd:boolean" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify whether the Sessions should be cached. Default is true.
[DEPRECATED] Consider wrapping you SessionFactory in the org.springframework.integration.file.remote.session.CachingSessionFactory.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>