From 7f17a58adfdd17f1b9b18fbc2d8d084e2fa9d7eb Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Fri, 24 Jul 2015 15:29:17 -0400 Subject: [PATCH 1/2] INT-3491: (S)FTP Delegation Session Factory JIRA: https://jira.spring.io/browse/INT-3491 Select the session factory at runtime. --- .../remote/session/CachingSessionFactory.java | 7 +- .../session/DefaultSessionFactoryFactory.java | 58 ++++++ .../session/DelegatingSessionFactory.java | 105 +++++++++++ .../remote/session/SessionFactoryFactory.java | 35 ++++ .../DelegatingSessionFactoryTests.java | 176 ++++++++++++++++++ .../delegating-session-factory-context.xml | 20 ++ src/reference/asciidoc/ftp.adoc | 29 +++ src/reference/asciidoc/sftp.adoc | 29 +++ src/reference/asciidoc/whats-new.adoc | 7 + 9 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryFactory.java create mode 100644 spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DelegatingSessionFactory.java create mode 100644 spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactory.java create mode 100644 spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/DelegatingSessionFactoryTests.java create mode 100644 spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/delegating-session-factory-context.xml 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 9314a0177f1..3c1c7971c2e 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-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -25,6 +25,7 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.integration.util.SimplePool; +import org.springframework.util.Assert; /** * A {@link SessionFactory} implementation that caches Sessions for reuse without @@ -62,6 +63,8 @@ public CachingSessionFactory(SessionFactory sessionFactory) { * Create a CachingSessionFactory with the specified session limit. By default, if * no sessions are available in the cache, and the size limit has been reached, * calling threads will block until a session is available. + *

+ * Do not cache a {@link DelegatingSessionFactory}, cache each delegate therein instead. * @see #setSessionWaitTimeout(long) * @see #setPoolSize(int) * @@ -69,6 +72,8 @@ public CachingSessionFactory(SessionFactory sessionFactory) { * @param sessionCacheSize The maximum cache size. */ public CachingSessionFactory(SessionFactory sessionFactory, int sessionCacheSize) { + Assert.isTrue(!(sessionFactory instanceof DelegatingSessionFactory), + "'sessionFactory' cannot be a 'DelegatingSessionFactory'; cache each delegate instead"); this.sessionFactory = sessionFactory; this.pool = new SimplePool>(sessionCacheSize, new SimplePool.PoolItemCallback>() { @Override diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryFactory.java new file mode 100644 index 00000000000..81001ac09a9 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.file.remote.session; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The default implementation of {@link SessionFactoryFactory} using a simple map lookup. + * + * @author Gary Russell + * @since 4.2 + * + */ +public class DefaultSessionFactoryFactory implements SessionFactoryFactory { + + private final Map> factories = new ConcurrentHashMap>(); + + private final SessionFactory defaultFactory; + + public DefaultSessionFactoryFactory(Map> factories) { + this(factories, null); + } + + public DefaultSessionFactoryFactory(Map> factories, Object defaultKey) { + this.factories.putAll(factories); + if (defaultKey != null) { + this.defaultFactory = factories.get(defaultKey); + } + else { + this.defaultFactory = null; + } + } + + + @Override + public SessionFactory getSessionFactory(Object key) { + if (key == null) { + return this.defaultFactory; + } + SessionFactory factory = this.factories.get(key); + return factory != null ? factory : this.defaultFactory; + } + +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DelegatingSessionFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DelegatingSessionFactory.java new file mode 100644 index 00000000000..ca7abb7fa23 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DelegatingSessionFactory.java @@ -0,0 +1,105 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.file.remote.session; + +import java.util.Map; + +import org.springframework.messaging.Message; +import org.springframework.util.Assert; + +/** + * {@link SessionFactory} that delegates to a {@link SessionFactory} retrieved from a + * {@link SessionFactoryFactory}. + * + * @author Gary Russell + * @since 4.2 + * + */ +public class DelegatingSessionFactory implements SessionFactory { + + private final SessionFactoryFactory factoryFactory; + + private final ThreadLocal threadKey = new ThreadLocal(); + + /** + * Construct an instance with a {@link DefaultSessionFactoryFactory} using the + * supplied factories and default key. + * @param factories the factories. + * @param defaultKey the default key. + */ + public DelegatingSessionFactory(Map> factories, Object defaultKey) { + this(new DefaultSessionFactoryFactory(factories, defaultKey)); + } + + /** + * Construct an instance using the supplied factory. + * @param factoryFactory the factory. + */ + public DelegatingSessionFactory(SessionFactoryFactory factoryFactory) { + Assert.notNull(factoryFactory, "'factoryFactory' cannot be null"); + this.factoryFactory = factoryFactory; + } + + /** + * Set a key to be used for {@link #getSession()} on this thread. + * @param key the key. + */ + public void setThreadKey(Object key) { + this.threadKey.set(key); + } + + /** + * Clear the key for this thread. + */ + public void clearThreadKey() { + this.threadKey.remove(); + } + + /** + * Messaging-friendly version of {@link #setThreadKey(Object)} that can be invoked from + * a service activator. + * @param message the message. + * @param key the key. + * @return the message (unchanged). + */ + public Message setThreadKey(Message message, Object key) { + this.threadKey.set(key); + return message; + } + + /** + * Messaging-friendly version of {@link #clearThreadKey()} that can be invoked from + * a service activator. + * @param message the message. + * @return the message (unchanged). + */ + public Message clearThreadKey(Message message) { + this.threadKey.remove(); + return message; + } + + @Override + public Session getSession() { + return getSession(this.threadKey.get()); + } + + public Session getSession(Object key) { + SessionFactory sessionFactory = this.factoryFactory.getSessionFactory(key); + Assert.notNull(sessionFactory, "No default SessionFactory configured"); + return sessionFactory.getSession(); + } + +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactory.java new file mode 100644 index 00000000000..1c828035707 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.file.remote.session; + +/** + * + * A factory returning a {@link SessionFactory} based on some key. + * + * @author Gary Russell + * @since 4.2 + * + */ +public interface SessionFactoryFactory { + + /** + * Return a {@link SessionFactory} for the key. + * @param key the key. + * @return the session factory. + */ + SessionFactory getSessionFactory(Object key); + +} diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/DelegatingSessionFactoryTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/DelegatingSessionFactoryTests.java new file mode 100644 index 00000000000..65c40cb47d1 --- /dev/null +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/DelegatingSessionFactoryTests.java @@ -0,0 +1,176 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.integration.file.remote.session; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.file.remote.AbstractFileInfo; +import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.PollableChannel; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Gary Russell + * @since 4.2 + * + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class DelegatingSessionFactoryTests { + + @Autowired + TestSessionFactory foo; + + @Autowired + TestSessionFactory bar; + + @Autowired + DelegatingSessionFactory dsf; + + @Autowired + MessageChannel in; + + @Autowired + PollableChannel out; + + @Test + public void testDelegates() { + assertEquals(foo.mockSession, dsf.getSession("foo")); + assertEquals(bar.mockSession, dsf.getSession("bar")); + assertEquals(bar.mockSession, dsf.getSession("junk")); + assertEquals(bar.mockSession, dsf.getSession()); + dsf.setThreadKey("foo"); + assertEquals(foo.mockSession, dsf.getSession("foo")); + dsf.clearThreadKey(); + } + + @Test + public void testFlow() throws Exception { + in.send(new GenericMessage("foo")); + Message received = out.receive(0); + assertNotNull(received); + verify(foo.mockSession).list("foo/"); + assertNull(TestUtils.getPropertyValue(dsf, "threadKey", ThreadLocal.class).get()); + } + + @Configuration + @ImportResource("classpath:/org/springframework/integration/file/remote/session/delegating-session-factory-context.xml") + @EnableIntegration + public static class Config { + + @Bean + TestSessionFactory foo() { + return new TestSessionFactory(); + } + + @Bean + TestSessionFactory bar() { + return new TestSessionFactory(); + } + + @Bean + DelegatingSessionFactory dsf() { + Map> factories = new HashMap>(); + factories.put("foo", foo()); + factories.put("bar", bar()); + SessionFactoryFactory sff = new DefaultSessionFactoryFactory(factories, "bar"); + return new DelegatingSessionFactory(sff); + } + + @ServiceActivator(inputChannel="c1") + @Bean + MessageHandler handler() { + AbstractRemoteFileOutboundGateway gateway = new AbstractRemoteFileOutboundGateway(dsf(), "ls", "payload") { + + @Override + protected boolean isDirectory(String file) { + return false; + } + + @Override + protected boolean isLink(String file) { + return false; + } + + @Override + protected String getFilename(String file) { + return file; + } + + @Override + protected String getFilename(AbstractFileInfo file) { + return file.getFilename(); + } + + @Override + protected long getModified(String file) { + return 0; + } + + @Override + protected List> asFileInfoList(Collection files) { + return null; + } + + @Override + protected String enhanceNameWithSubDirectory(String file, String directory) { + return null; + } + }; + gateway.setOutputChannelName("c2"); + gateway.setOptions("-1"); + return gateway; + } + + } + + private static class TestSessionFactory implements SessionFactory { + + @SuppressWarnings("unchecked") + private final Session mockSession = mock(Session.class); + + @Override + public Session getSession() { + return this.mockSession ; + } + + } + +} diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/delegating-session-factory-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/delegating-session-factory-context.xml new file mode 100644 index 00000000000..63333b37b35 --- /dev/null +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/delegating-session-factory-context.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/src/reference/asciidoc/ftp.adoc b/src/reference/asciidoc/ftp.adoc index 4ed581e22f5..5f3aea6f38f 100644 --- a/src/reference/asciidoc/ftp.adoc +++ b/src/reference/asciidoc/ftp.adoc @@ -121,6 +121,35 @@ public class AdvancedFtpSessionFactory extends DefaultFtpSessionFactory { } ---- +[[ftp-dsf]] +=== Delegating Session Factory + +_Version 4.2_ introduced the `DelegatingSessionFactory` which allows the selection of the actual session factory at +runtime. +Prior to invoking the ftp endpoint, call `setThreadKey()` on the factory to associate a key with the current thread. +That key is then used to lookup the actual session factory to be used. +The key can be cleared by calling `clearThreadKey()` after use. + +Convenience methods have been added so this can easily be done from a message flow: + +[source, xml] +---- + + + + + + + + + +---- + +IMPORTANT: When using session caching (see <>), each of the delegates should be cached; you +cannot cache the `DelegatingSessionFactory` itself. + [[ftp-inbound]] === FTP Inbound Channel Adapter diff --git a/src/reference/asciidoc/sftp.adoc b/src/reference/asciidoc/sftp.adoc index 26ee8c08dc6..27f9908efa5 100644 --- a/src/reference/asciidoc/sftp.adoc +++ b/src/reference/asciidoc/sftp.adoc @@ -159,6 +159,35 @@ Defaults to `0`, which means, that no timeout will occur. The remote user to use. _Mandatory_. +[[sftp-dsf]] +=== Delegating Session Factory + +_Version 4.2_ introduced the `DelegatingSessionFactory` which allows the selection of the actual session factory at +runtime. +Prior to invoking the ftp endpoint, call `setThreadKey()` on the factory to associate a key with the current thread. +That key is then used to lookup the actual session factory to be used. +The key can be cleared by calling `clearThreadKey()` after use. + +Convenience methods have been added so this can easily be done from a message flow: + +[source, xml] +---- + + + + + + + + + +---- + +IMPORTANT: When using session caching (see <>), each of the delegates should be cached; you +cannot cache the `DelegatingSessionFactory` itself. + [[sftp-session-caching]] === SFTP Session Caching diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index f99c1988ea0..288ed33561e 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -270,6 +270,13 @@ occur after part of the request is completed. If such a condition occurs, a `PartialSuccessException` is thrown containing the partial results. See <> and <> for more information. +===== Delegating Session Factory + +A delegating session factory is now available, enabling the selection of a particular session factory based on some +thread context value. + +See <> and <> for more information. + ==== Websocket Changes `WebSocketHandlerDecoratorFactory` support has been added to the `ServerWebSocketContainer` From b18d2a75a9b6c3d1e3dcdde2981ef18edb873e4e Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Mon, 27 Jul 2015 17:36:18 -0400 Subject: [PATCH 2/2] Polishing; PR Comments Also add/remove factories. --- ...java => DefaultSessionFactoryLocator.java} | 40 ++++++++++++++----- .../session/DelegatingSessionFactory.java | 30 +++++++++----- ...actory.java => SessionFactoryLocator.java} | 2 +- .../DelegatingSessionFactoryTests.java | 37 ++++++++++++----- src/reference/asciidoc/ftp.adoc | 6 ++- src/reference/asciidoc/sftp.adoc | 6 ++- 6 files changed, 87 insertions(+), 34 deletions(-) rename spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/{DefaultSessionFactoryFactory.java => DefaultSessionFactoryLocator.java} (54%) rename spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/{SessionFactoryFactory.java => SessionFactoryLocator.java} (95%) diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryLocator.java similarity index 54% rename from spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryFactory.java rename to spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryLocator.java index 81001ac09a9..556800088f7 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryFactory.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DefaultSessionFactoryLocator.java @@ -19,32 +19,52 @@ import java.util.concurrent.ConcurrentHashMap; /** - * The default implementation of {@link SessionFactoryFactory} using a simple map lookup. + * The default implementation of {@link SessionFactoryLocator} using a simple map lookup + * and an optional default to fall back on. * * @author Gary Russell * @since 4.2 * */ -public class DefaultSessionFactoryFactory implements SessionFactoryFactory { +public class DefaultSessionFactoryLocator implements SessionFactoryLocator { private final Map> factories = new ConcurrentHashMap>(); private final SessionFactory defaultFactory; - public DefaultSessionFactoryFactory(Map> factories) { + /** + * @param factories A map of factories, keyed by lookup key. + */ + public DefaultSessionFactoryLocator(Map> factories) { this(factories, null); } - public DefaultSessionFactoryFactory(Map> factories, Object defaultKey) { + /** + * @param factories A map of factories, keyed by lookup key. + * @param defaultFactory A default to be used if the lookup fails. + */ + public DefaultSessionFactoryLocator(Map> factories, SessionFactory defaultFactory) { this.factories.putAll(factories); - if (defaultKey != null) { - this.defaultFactory = factories.get(defaultKey); - } - else { - this.defaultFactory = null; - } + this.defaultFactory = defaultFactory; } + /** + * Add a session factory. + * @param key the lookup key. + * @param factory the factory. + */ + public void addSessionFactory(String key, SessionFactory factory) { + this.factories.put(key, factory); + } + + /** + * Remove a session factory. + * @param key the lookup key. + * @return the factory, if it was present. + */ + public SessionFactory removeSessionFactory(Object key) { + return this.factories.remove(key); + } @Override public SessionFactory getSessionFactory(Object key) { diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DelegatingSessionFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DelegatingSessionFactory.java index ca7abb7fa23..2d02c58cf79 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DelegatingSessionFactory.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/DelegatingSessionFactory.java @@ -22,7 +22,7 @@ /** * {@link SessionFactory} that delegates to a {@link SessionFactory} retrieved from a - * {@link SessionFactoryFactory}. + * {@link SessionFactoryLocator}. * * @author Gary Russell * @since 4.2 @@ -30,27 +30,35 @@ */ public class DelegatingSessionFactory implements SessionFactory { - private final SessionFactoryFactory factoryFactory; + private final SessionFactoryLocator factoryLocator; private final ThreadLocal threadKey = new ThreadLocal(); /** - * Construct an instance with a {@link DefaultSessionFactoryFactory} using the + * Construct an instance with a {@link DefaultSessionFactoryLocator} using the * supplied factories and default key. * @param factories the factories. - * @param defaultKey the default key. + * @param defaultFactory the default to use if the lookup fails. */ - public DelegatingSessionFactory(Map> factories, Object defaultKey) { - this(new DefaultSessionFactoryFactory(factories, defaultKey)); + public DelegatingSessionFactory(Map> factories, SessionFactory defaultFactory) { + this(new DefaultSessionFactoryLocator(factories, defaultFactory)); } /** * Construct an instance using the supplied factory. - * @param factoryFactory the factory. + * @param factoryLocator the factory. */ - public DelegatingSessionFactory(SessionFactoryFactory factoryFactory) { - Assert.notNull(factoryFactory, "'factoryFactory' cannot be null"); - this.factoryFactory = factoryFactory; + public DelegatingSessionFactory(SessionFactoryLocator factoryLocator) { + Assert.notNull(factoryLocator, "'factoryFactory' cannot be null"); + this.factoryLocator = factoryLocator; + } + + /** + * Return this factory's locator. + * @return the locator. + */ + public SessionFactoryLocator getFactoryLocator() { + return factoryLocator; } /** @@ -97,7 +105,7 @@ public Session getSession() { } public Session getSession(Object key) { - SessionFactory sessionFactory = this.factoryFactory.getSessionFactory(key); + SessionFactory sessionFactory = this.factoryLocator.getSessionFactory(key); Assert.notNull(sessionFactory, "No default SessionFactory configured"); return sessionFactory.getSession(); } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryLocator.java similarity index 95% rename from spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactory.java rename to spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryLocator.java index 1c828035707..cb5785dc788 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactory.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryLocator.java @@ -23,7 +23,7 @@ * @since 4.2 * */ -public interface SessionFactoryFactory { +public interface SessionFactoryLocator { /** * Return a {@link SessionFactory} for the key. diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/DelegatingSessionFactoryTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/DelegatingSessionFactoryTests.java index 65c40cb47d1..61d3202b089 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/DelegatingSessionFactoryTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/DelegatingSessionFactoryTests.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -70,15 +71,24 @@ public class DelegatingSessionFactoryTests { @Autowired PollableChannel out; + @Autowired + DefaultSessionFactoryLocator sessionFactoryLocator; + @Test public void testDelegates() { - assertEquals(foo.mockSession, dsf.getSession("foo")); - assertEquals(bar.mockSession, dsf.getSession("bar")); - assertEquals(bar.mockSession, dsf.getSession("junk")); - assertEquals(bar.mockSession, dsf.getSession()); - dsf.setThreadKey("foo"); - assertEquals(foo.mockSession, dsf.getSession("foo")); - dsf.clearThreadKey(); + assertEquals(foo.mockSession, this.dsf.getSession("foo")); + assertEquals(bar.mockSession, this.dsf.getSession("bar")); + assertEquals(bar.mockSession, this.dsf.getSession("junk")); + assertEquals(bar.mockSession, this.dsf.getSession()); + this.dsf.setThreadKey("foo"); + assertEquals(foo.mockSession, this.dsf.getSession("foo")); + this.dsf.clearThreadKey(); + TestSessionFactory factory = new TestSessionFactory(); + this.sessionFactoryLocator.addSessionFactory("baz", factory); + this.dsf.setThreadKey("baz"); + assertEquals(factory.mockSession, this.dsf.getSession("baz")); + this.dsf.clearThreadKey(); + assertSame(factory, sessionFactoryLocator.removeSessionFactory("baz")); } @Test @@ -107,11 +117,18 @@ TestSessionFactory bar() { @Bean DelegatingSessionFactory dsf() { + SessionFactoryLocator sff = sessionFactoryLocator(); + return new DelegatingSessionFactory(sff); + } + + @Bean + public SessionFactoryLocator sessionFactoryLocator() { Map> factories = new HashMap>(); factories.put("foo", foo()); - factories.put("bar", bar()); - SessionFactoryFactory sff = new DefaultSessionFactoryFactory(factories, "bar"); - return new DelegatingSessionFactory(sff); + TestSessionFactory bar = bar(); + factories.put("bar", bar); + SessionFactoryLocator sff = new DefaultSessionFactoryLocator(factories, bar); + return sff; } @ServiceActivator(inputChannel="c1") diff --git a/src/reference/asciidoc/ftp.adoc b/src/reference/asciidoc/ftp.adoc index 5f3aea6f38f..9ec6197303e 100644 --- a/src/reference/asciidoc/ftp.adoc +++ b/src/reference/asciidoc/ftp.adoc @@ -135,7 +135,11 @@ Convenience methods have been added so this can easily be done from a message fl [source, xml] ---- - + + + + + - + + + + +