http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
new file mode 100644
index 0000000..6420411
--- /dev/null
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sshd.server.subsystem.sftp;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class SftpSubsystemFactoryTest extends BaseTestSupport {
+    public SftpSubsystemFactoryTest() {
+        super();
+    }
+
+    /**
+     * Make sure that the builder returns a factory with the default values
+     * if no {@code withXXX} method is invoked
+     */
+    @Test
+    public void testBuilderDefaultFactoryValues() {
+        SftpSubsystemFactory factory = new 
SftpSubsystemFactory.Builder().build();
+        assertNull("Mismatched executor", factory.getExecutorService());
+        assertFalse("Mismatched shutdown state", factory.isShutdownOnExit());
+        assertSame("Mismatched unsupported attribute policy", 
SftpSubsystemFactory.DEFAULT_POLICY, factory.getUnsupportedAttributePolicy());
+    }
+
+    /**
+     * Make sure that the builder initializes correctly the built factory
+     */
+    @Test
+    public void testBuilderCorrectlyInitializesFactory() {
+        SftpSubsystemFactory.Builder builder = new 
SftpSubsystemFactory.Builder();
+        ExecutorService service = dummyExecutor();
+        SftpSubsystemFactory factory = builder.withExecutorService(service)
+                .withShutdownOnExit(true)
+                .build();
+        assertSame("Mismatched executor", service, 
factory.getExecutorService());
+        assertTrue("Mismatched shutdown state", factory.isShutdownOnExit());
+
+        for (UnsupportedAttributePolicy policy : 
UnsupportedAttributePolicy.VALUES) {
+            SftpSubsystemFactory actual = 
builder.withUnsupportedAttributePolicy(policy).build();
+            assertSame("Mismatched unsupported attribute policy", policy, 
actual.getUnsupportedAttributePolicy());
+        }
+    }
+
+    /**
+     * <UL>
+     * <LI>
+     * Make sure the builder returns new instances on every call to
+     * {@link SftpSubsystemFactory.Builder#build()} method
+     * </LI>
+     *
+     * <LI>
+     * Make sure values are preserved between successive invocations
+     * of the {@link SftpSubsystemFactory.Builder#build()} method
+     * </LI>
+     * </UL
+     */
+    @Test
+    public void testBuilderUniqueInstance() {
+        SftpSubsystemFactory.Builder builder = new 
SftpSubsystemFactory.Builder();
+        SftpSubsystemFactory f1 = 
builder.withExecutorService(dummyExecutor()).build();
+        SftpSubsystemFactory f2 = builder.build();
+        assertNotSame("No new instance built", f1, f2);
+        assertSame("Mismatched executors", f1.getExecutorService(), 
f2.getExecutorService());
+
+        SftpSubsystemFactory f3 = 
builder.withExecutorService(dummyExecutor()).build();
+        assertNotSame("Executor service not changed", f1.getExecutorService(), 
f3.getExecutorService());
+    }
+
+    private static ExecutorService dummyExecutor() {
+        return Mockito.mock(ExecutorService.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
----------------------------------------------------------------------
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
new file mode 100644
index 0000000..e6b10e0
--- /dev/null
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SshFsMounter.java
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sshd.server.subsystem.sftp;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.config.SshConfigFileReader;
+import org.apache.sshd.common.io.BuiltinIoServiceFactoryFactories;
+import org.apache.sshd.common.io.IoServiceFactory;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.common.util.threads.ThreadUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.password.AcceptAllPasswordAuthenticator;
+import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.scp.ScpCommandFactory;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
+import org.apache.sshd.util.test.Utils;
+
+/**
+ * A basic implementation to allow remote mounting of the local file system 
via SFTP
+ *
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public final class SshFsMounter {
+    public static class MounterCommand extends AbstractLoggingBean implements 
Command, SessionAware, Runnable {
+        private final String command;
+        private final String cmdName;
+        private final List<String> args;
+        private String username;
+        private InputStream stdin;
+        private PrintStream stdout;
+        private PrintStream stderr;
+        private ExitCallback callback;
+        private ExecutorService executor;
+        private Future<?> future;
+
+        public MounterCommand(String command) {
+            this.command = ValidateUtils.checkNotNullAndNotEmpty(command, "No 
command");
+
+            String[] comps = GenericUtils.split(this.command, ' ');
+            int numComps = GenericUtils.length(comps);
+            cmdName = 
GenericUtils.trimToEmpty(ValidateUtils.checkNotNullAndNotEmpty(comps[0], "No 
command name"));
+            if (numComps > 1) {
+                args = new ArrayList<>(numComps - 1);
+                for (int index = 1; index < numComps; index++) {
+                    String c = GenericUtils.trimToEmpty(comps[index]);
+                    if (GenericUtils.isEmpty(c)) {
+                        continue;
+                    }
+
+                    args.add(c);
+                }
+            } else {
+                args = Collections.emptyList();
+            }
+
+            log.info("<init>(" + command + ")");
+        }
+
+        @Override
+        public void run() {
+            try {
+                log.info("run(" + username + ")[" + command + "] start");
+                if ("id".equals(cmdName)) {
+                    int numArgs = GenericUtils.size(args);
+                    if (numArgs <= 0) {
+                        stdout.println("uid=0(root) gid=0(root) 
groups=0(root)");
+                    } else if (numArgs == 1) {
+                        String modifier = args.get(0);
+                        if ("-u".equals(modifier) || "-G".equals(modifier)) {
+                            stdout.println("0");
+                        } else {
+                            throw new IllegalArgumentException("Unknown 
modifier: " + modifier);
+                        }
+                    } else {
+                        throw new IllegalArgumentException("Unexpected extra 
command arguments");
+                    }
+                } else {
+                    throw new UnsupportedOperationException("Unknown command");
+                }
+
+                log.info("run(" + username + ")[" + command + "] end");
+                callback.onExit(0);
+            } catch (Exception e) {
+                log.error("run(" + username + ")[" + command + "] " + 
e.getClass().getSimpleName() + ": " + e.getMessage(), e);
+                stderr.append(e.getClass().getSimpleName()).append(": 
").println(e.getMessage());
+                callback.onExit(-1, e.toString());
+            }
+        }
+
+        @Override
+        public void setSession(ServerSession session) {
+            username = session.getUsername();
+        }
+
+        @Override
+        public void setInputStream(InputStream in) {
+            this.stdin = in;
+        }
+
+        @Override
+        public void setOutputStream(OutputStream out) {
+            this.stdout = new PrintStream(out, true);
+        }
+
+        @Override
+        public void setErrorStream(OutputStream err) {
+            this.stderr = new PrintStream(err, true);
+        }
+
+        @Override
+        public void setExitCallback(ExitCallback callback) {
+            this.callback = callback;
+        }
+
+        @Override
+        public void start(Environment env) throws IOException {
+            executor = 
ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
+            future = executor.submit(this);
+        }
+
+        @Override
+        public void destroy() {
+            stopCommand();
+
+            if (stdout != null) {
+                try {
+                    log.info("destroy(" + username + ")[" + command + "] close 
stdout");
+                    stdout.close();
+                    log.info("destroy(" + username + ")[" + command + "] 
stdout closed");
+                } finally {
+                    stdout = null;
+                }
+            }
+
+            if (stderr != null) {
+                try {
+                    log.info("destroy(" + username + ")[" + command + "] close 
stderr");
+                    stderr.close();
+                    log.info("destroy(" + username + ")[" + command + "] 
stderr closed");
+                } finally {
+                    stderr = null;
+                }
+            }
+
+            if (stdin != null) {
+                try {
+                    log.info("destroy(" + username + ")[" + command + "] close 
stdin");
+                    stdin.close();
+                    log.info("destroy(" + username + ")[" + command + "] stdin 
closed");
+                } catch (IOException e) {
+                    log.warn("destroy(" + username + ")[" + command + "] 
failed (" + e.getClass().getSimpleName() + ") to close stdin: " + 
e.getMessage());
+                    if (log.isDebugEnabled()) {
+                        log.debug("destroy(" + username + ")[" + command + "] 
failure details", e);
+                    }
+                } finally {
+                    stdin = null;
+                }
+            }
+        }
+
+        private void stopCommand() {
+            if ((future != null) && (!future.isDone())) {
+                try {
+                    log.info("stopCommand(" + username + ")[" + command + "] 
cancelling");
+                    future.cancel(true);
+                    log.info("stopCommand(" + username + ")[" + command + "] 
cancelled");
+                } finally {
+                    future = null;
+                }
+            }
+
+            if ((executor != null) && (!executor.isShutdown())) {
+                try {
+                    log.info("stopCommand(" + username + ")[" + command + "] 
shutdown executor");
+                    executor.shutdownNow();
+                    log.info("stopCommand(" + username + ")[" + command + "] 
executor shut down");
+                } finally {
+                    executor = null;
+                }
+            }
+        }
+    }
+
+    public static class MounterCommandFactory implements CommandFactory {
+        public static final MounterCommandFactory INSTANCE = new 
MounterCommandFactory();
+
+        public MounterCommandFactory() {
+            super();
+        }
+
+        @Override
+        public Command createCommand(String command) {
+            return new MounterCommand(command);
+        }
+    }
+
+    private SshFsMounter() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+
+    public static void main(String[] args) throws Exception {
+        int port = SshConfigFileReader.DEFAULT_PORT;
+        boolean error = false;
+        Map<String, Object> options = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        int numArgs = GenericUtils.length(args);
+        for (int i = 0; i < numArgs; i++) {
+            String argName = args[i];
+            if ("-p".equals(argName)) {
+                if ((i + 1) >= numArgs) {
+                    System.err.println("option requires an argument: " + 
argName);
+                    break;
+                }
+                port = Integer.parseInt(args[++i]);
+            } else if ("-io".equals(argName)) {
+                if (i + 1 >= numArgs) {
+                    System.err.println("option requires an argument: " + 
argName);
+                    break;
+                }
+
+                String provider = args[++i];
+                if ("mina".equals(provider)) {
+                    System.setProperty(IoServiceFactory.class.getName(), 
BuiltinIoServiceFactoryFactories.MINA.getFactoryClassName());
+                } else if ("nio2".endsWith(provider)) {
+                    System.setProperty(IoServiceFactory.class.getName(), 
BuiltinIoServiceFactoryFactories.NIO2.getFactoryClassName());
+                } else {
+                    System.err.println("provider should be mina or nio2: " + 
argName);
+                    error = true;
+                    break;
+                }
+            } else if ("-o".equals(argName)) {
+                if ((i + 1) >= numArgs) {
+                    System.err.println("option requires and argument: " + 
argName);
+                    error = true;
+                    break;
+                }
+                String opt = args[++i];
+                int idx = opt.indexOf('=');
+                if (idx <= 0) {
+                    System.err.println("bad syntax for option: " + opt);
+                    error = true;
+                    break;
+                }
+                options.put(opt.substring(0, idx), opt.substring(idx + 1));
+            } else if (argName.startsWith("-")) {
+                System.err.println("illegal option: " + argName);
+                error = true;
+                break;
+            } else {
+                System.err.println("extra argument: " + argName);
+                error = true;
+                break;
+            }
+        }
+        if (error) {
+            System.err.println("usage: sshfs [-p port] [-io mina|nio2] [-o 
option=value]");
+            System.exit(-1);
+        }
+
+        SshServer sshd = Utils.setupTestServer(SshFsMounter.class);
+        Map<String, Object> props = sshd.getProperties();
+        props.putAll(options);
+        PropertyResolver resolver = 
PropertyResolverUtils.toPropertyResolver(options);
+        File targetFolder = 
Objects.requireNonNull(Utils.detectTargetFolder(MounterCommandFactory.class), 
"Failed to detect target folder");
+        if (SecurityUtils.isBouncyCastleRegistered()) {
+            
sshd.setKeyPairProvider(SecurityUtils.createGeneratorHostKeyProvider(new 
File(targetFolder, "key.pem").toPath()));
+        } else {
+            sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new 
File(targetFolder, "key.ser")));
+        }
+        // Should come AFTER key pair provider setup so auto-welcome can be 
generated if needed
+        SshServer.setupServerBanner(sshd, resolver);
+
+        sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
+        sshd.setPasswordAuthenticator(AcceptAllPasswordAuthenticator.INSTANCE);
+        sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
+        sshd.setCommandFactory(new 
ScpCommandFactory.Builder().withDelegate(MounterCommandFactory.INSTANCE).build());
+        sshd.setSubsystemFactories(Collections.singletonList(new 
SftpSubsystemFactory()));
+        sshd.setPort(port);
+
+        System.err.println("Starting SSHD on port " + port);
+        sshd.start();
+        Thread.sleep(Long.MAX_VALUE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-spring-sftp/pom.xml
----------------------------------------------------------------------
diff --git a/sshd-spring-sftp/pom.xml b/sshd-spring-sftp/pom.xml
index 2f9e442..01cbf6a 100644
--- a/sshd-spring-sftp/pom.xml
+++ b/sshd-spring-sftp/pom.xml
@@ -55,6 +55,11 @@
             <artifactId>sshd-core</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-sftp</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <!-- Replacement of commons-logging for Spring parts that still use it 
-->
         <dependency>
             <groupId>org.slf4j</groupId>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
----------------------------------------------------------------------
diff --git 
a/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
 
b/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
index 85aecec..d5a76a7 100644
--- 
a/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
+++ 
b/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
@@ -34,6 +34,7 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.simple.SimpleClientConfigurator;
 import org.apache.sshd.client.subsystem.sftp.SftpClient;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
 import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.config.SshConfigFileReader;
@@ -338,7 +339,7 @@ public class ApacheSshdSftpSessionFactory
                 session = resolveClientSession(sharedInstance);
 
                 SftpVersionSelector selector = getSftpVersionSelector();
-                SftpClient sftpClient = session.createSftpClient(selector);
+                SftpClient sftpClient = 
SftpClientFactory.instance().createSftpClient(session, selector);
                 try {
                     ClientSession sessionInstance = session;
                     Session<DirEntry> result = sharedInstance

Reply via email to