You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/11/11 16:50:49 UTC

[1/8] mina-sshd git commit: [SSHD-861] Add support for password-less login via SftpFileSystemProvider

Repository: mina-sshd
Updated Branches:
  refs/heads/master c64bd7a95 -> 44576a59a


[SSHD-861] Add support for password-less login via SftpFileSystemProvider


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/44576a59
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/44576a59
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/44576a59

Branch: refs/heads/master
Commit: 44576a59a54df08ee28400bdac4ed40642623dd4
Parents: e3b8acd
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 11 11:27:36 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 11 18:56:28 2018 +0200

----------------------------------------------------------------------
 README.md                                       |  8 +++++++-
 .../SftpFileSystemClientSessionInitializer.java |  8 ++++++--
 .../sftp/fs/SftpFileSystemProvider.java         | 20 ++++++++++++++------
 .../sftp/fs/SftpFileSystemURITest.java          | 10 ++++++++++
 4 files changed, 37 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/44576a59/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index f8bfa56..720c3e9 100644
--- a/README.md
+++ b/README.md
@@ -1028,7 +1028,13 @@ the (default) password-based one:
         public void authenticateClientSession(
                 SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
                     throws IOException {
-            // Set up password-less login instead of password-based
+            /*
+             * Set up password-less login instead of password-based using the specified key
+             *
+             * Note: if SSH client and/or session already have a KeyPairProvider set up and the code
+             * knows that these keys are already registered with the remote server, then no need to
+             * add the public key identitiy - can simply call sesssion.auth().verify(context.getMaxAuthTime()).
+             */
             KeyPair kp = ... obtain a registered key-pair...
             session.addPublicKeyIdentity(kp);
             return sesssion.auth().verify(context.getMaxAuthTime());

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/44576a59/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
index 576e302..3524110 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
@@ -73,8 +73,12 @@ public interface SftpFileSystemClientSessionInitializer {
     default void authenticateClientSession(
             SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
                 throws IOException {
-        PasswordHolder password = context.getCredentials();
-        session.addPasswordIdentity(password.getPassword());
+        PasswordHolder passwordHolder = context.getCredentials();
+        String password = passwordHolder.getPassword();
+        // If no password provided perhaps the client is set-up to use registered public keys
+        if (password != null) {
+            session.addPasswordIdentity(password);
+        }
         session.auth().verify(context.getMaxAuthTime());
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/44576a59/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
index fdec2f3..5acf6da 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
@@ -354,9 +354,12 @@ public class SftpFileSystemProvider extends FileSystemProvider {
             return null;
         }
 
-        String[] ui = GenericUtils.split(userInfo, ':');
-        ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
-        return new BasicCredentialsImpl(ui[0], ui[1]);
+        int pos = userInfo.indexOf(':');
+        if (pos < 0) {
+            return new BasicCredentialsImpl(userInfo, null);    // assume password-less login
+        }
+
+        return new BasicCredentialsImpl(userInfo.substring(0, pos), userInfo.substring(pos + 1));
     }
 
     public static Map<String, Object> parseURIParameters(URI uri) {
@@ -1320,7 +1323,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
 
     public static String encodeCredentials(String username, String password) {
         ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided");
-        ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided");
+
         /*
          * There is no way to properly encode/decode credentials that already contain
          * colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1:
@@ -1334,7 +1337,12 @@ public class SftpFileSystemProvider extends FileSystemProvider {
          *      reject such data when it is received as part of a reference and
          *      should reject the storage of such data in unencrypted form.
          */
-        ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials");
-        return username + ":" + password;
+        ValidateUtils.checkTrue((username.indexOf(':') < 0) && ((password == null) || (password.indexOf(':') < 0)),
+            "Reserved character used in credentials");
+        if (password == null) {
+            return username;    // assume password-less login required
+        } else {
+            return username + ":" + password;
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/44576a59/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
index a8bfaf8..02eb835 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
@@ -88,6 +88,7 @@ public class SftpFileSystemURITest extends JUnitTestSupport {
                     }
                 });
                 add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null});
+                add(new Object[] {"19.65.7.3", 0, "user", null, null});
             }
         };
     }
@@ -108,6 +109,15 @@ public class SftpFileSystemURITest extends JUnitTestSupport {
         assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString()));
     }
 
+    @Test
+    public void testEncodeDecodeCredentials() {
+        String userInfo = SftpFileSystemProvider.encodeCredentials(username, password);
+        BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(userInfo);
+        assertNotNull("No credentials provided", credentials);
+        assertEquals("Mismatched user", username, credentials.getUsername());
+        assertEquals("Mismatched password", password, credentials.getPassword());
+    }
+
     @Override
     public String toString() {
         return getClass().getSimpleName()


[7/8] mina-sshd git commit: [SSHD-861] Added SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider

Posted by lg...@apache.org.
[SSHD-861] Added SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/e3b8acd7
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/e3b8acd7
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/e3b8acd7

Branch: refs/heads/master
Commit: e3b8acd7e068fdc586f1f825eb675a335eb42824
Parents: c24635d
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 11 10:43:16 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 11 18:56:28 2018 +0200

----------------------------------------------------------------------
 CHANGES.md                                      |    4 +
 README.md                                       |   34 +-
 .../apache/sshd/cli/client/SftpCommandMain.java |    2 +-
 .../java.nio.file.spi.FileSystemProvider        |    2 +-
 .../sftp/SftpAclFileAttributeView.java          |   67 -
 .../subsystem/sftp/SftpClientFactory.java       |   16 +-
 .../subsystem/sftp/SftpDirectoryStream.java     |   65 -
 .../client/subsystem/sftp/SftpFileStore.java    |  105 --
 .../client/subsystem/sftp/SftpFileSystem.java   |  600 --------
 .../subsystem/sftp/SftpFileSystemChannel.java   |   37 -
 .../subsystem/sftp/SftpFileSystemProvider.java  | 1312 -----------------
 .../sshd/client/subsystem/sftp/SftpPath.java    |   43 -
 .../client/subsystem/sftp/SftpPathIterator.java |   82 --
 .../sftp/SftpPosixFileAttributeView.java        |   94 --
 .../subsystem/sftp/SftpPosixFileAttributes.java |  113 --
 .../sftp/fs/SftpAclFileAttributeView.java       |   68 +
 .../subsystem/sftp/fs/SftpDirectoryStream.java  |   67 +
 .../client/subsystem/sftp/fs/SftpFileStore.java |  105 ++
 .../subsystem/sftp/fs/SftpFileSystem.java       |  604 ++++++++
 .../sftp/fs/SftpFileSystemChannel.java          |   40 +
 .../SftpFileSystemClientSessionInitializer.java |   97 ++
 .../fs/SftpFileSystemInitializationContext.java |  142 ++
 .../sftp/fs/SftpFileSystemProvider.java         | 1340 ++++++++++++++++++
 .../sshd/client/subsystem/sftp/fs/SftpPath.java |   43 +
 .../subsystem/sftp/fs/SftpPathIterator.java     |   84 ++
 .../sftp/fs/SftpPosixFileAttributeView.java     |   95 ++
 .../sftp/fs/SftpPosixFileAttributes.java        |  113 ++
 .../impl/AbstractSftpFileAttributeView.java     |    6 +-
 .../sftp/impl/DefaultSftpClientFactory.java     |    4 +-
 .../subsystem/sftp/SftpFileSystemTest.java      |  497 -------
 .../subsystem/sftp/SftpFileSystemURITest.java   |  121 --
 .../subsystem/sftp/fs/SftpFileSystemTest.java   |  532 +++++++
 .../sftp/fs/SftpFileSystemURITest.java          |  121 ++
 33 files changed, 3503 insertions(+), 3152 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/CHANGES.md
----------------------------------------------------------------------
diff --git a/CHANGES.md b/CHANGES.md
index f909f44..2a338cc 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -27,6 +27,10 @@ user to try and repeat an encrypted private key decoding using a different passw
 
 * `SshAgent#getIdentities` returns an `Iterable` rather than a `List`
 
+* `SftpFileSystemProvider` and its associated helper classes have been moved to
+`org.apache.sshd.client.subsystem.sftp.fs` package
+
+* Added `SftpFileSystemClientSessionInitializer` support in `SftpFileSystemProvider`
 
 ## Behavioral changes
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index c5edc53..f8bfa56 100644
--- a/README.md
+++ b/README.md
@@ -957,9 +957,16 @@ system.
 It is highly recommended to `close()` the mounted file system once no longer necessary in order to release the
 associated SFTP session sooner rather than later - e.g., via a `try-with-resource` code block.
 
+**Caveat:** Due to URI encoding of the username/password as a basic authentication, the system currently
+does not allow colon (`:`) in either one in order to avoid parsing confusion. See [RFC 3986 - section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1):
+
+>> Use of the format "user:password" in the userinfo field is
+>> deprecated ... Applications may choose to ignore or reject such
+>> data when it is received as part of a reference...
+
 #### Configuring the `SftpFileSystemProvider`
 
-When "mounting" a new file system one can provide configuration parameters using either the
+When "mounting" a new file system one can provide extra configuration parameters using either the
 environment map in the [FileSystems#newFileSystem](https://docs.oracle.com/javase/8/docs/api/java/nio/file/FileSystems.html#newFileSystem)
 method or via the URI query parameters. See the `SftpFileSystemProvider` for the available
 configuration keys and values.
@@ -1005,6 +1012,31 @@ configuration keys and values.
 
 ```
 
+#### Configuring the client session used to create an `SftpFileSystem`
+
+It is possible to register a `SftpFileSystemClientSessionInitializer` with the provider instead of the default one
+and thus better control the `ClientSession` used to generate the file-system instance. The default implementation
+simply connects and authenticates before creating a default `SftpFileSystem` instance. Users may wish
+to override some options or provide their own - e.g., execute a password-less authentication instead of
+the (default) password-based one:
+
+```java
+
+    SftpFileSystemProvider provider = ... obtain/create a provider ...
+    provider.setSftpFileSystemClientSessionInitializer(new SftpFileSystemClientSessionInitializer() {
+        @Override
+        public void authenticateClientSession(
+                SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
+                    throws IOException {
+            // Set up password-less login instead of password-based
+            KeyPair kp = ... obtain a registered key-pair...
+            session.addPublicKeyIdentity(kp);
+            return sesssion.auth().verify(context.getMaxAuthTime());
+        }
+    });
+
+```
+
 #### Tracking accessed location via `SftpFileSystemAccessor`
 
 One can override the default `SftpFileSystemAccessor` and thus be able to track all opened files and folders

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index e730448..99427ff 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -47,9 +47,9 @@ import org.apache.sshd.client.subsystem.sftp.SftpClient;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
 import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
 import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
 import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatExtensionInfo;
 import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatPathExtension;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
 import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.ServiceFactory;
 import org.apache.sshd.common.channel.ChannelFactory;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider b/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
index 75fea68..6d92ee2 100644
--- a/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
+++ b/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
@@ -17,4 +17,4 @@
 ## under the License.
 ##
 
-org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider
+org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
deleted file mode 100644
index 7cada6e..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.UserPrincipal;
-import java.util.List;
-
-import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpAclFileAttributeView extends AbstractSftpFileAttributeView implements AclFileAttributeView {
-    public SftpAclFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
-        super(provider, path, options);
-    }
-
-    @Override
-    public UserPrincipal getOwner() throws IOException {
-        PosixFileAttributes v = provider.readAttributes(path, PosixFileAttributes.class, options);
-        return v.owner();
-    }
-
-    @Override
-    public void setOwner(UserPrincipal owner) throws IOException {
-        provider.setAttribute(path, "posix", "owner", owner, options);
-    }
-
-    @Override
-    public String name() {
-        return "acl";
-    }
-
-    @Override
-    public List<AclEntry> getAcl() throws IOException {
-        return readRemoteAttributes().getAcl();
-    }
-
-    @Override
-    public void setAcl(List<AclEntry> acl) throws IOException {
-        writeRemoteAttributes(new SftpClient.Attributes().acl(acl));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
index 5497c91..0672482 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
@@ -19,9 +19,9 @@
 package org.apache.sshd.client.subsystem.sftp;
 
 import java.io.IOException;
-import java.nio.file.FileSystem;
 
 import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
 import org.apache.sshd.client.subsystem.sftp.impl.DefaultSftpClientFactory;
 
 /**
@@ -67,23 +67,23 @@ public interface SftpClientFactory {
      */
     SftpClient createSftpClient(ClientSession session, SftpVersionSelector selector) throws IOException;
 
-    default FileSystem createSftpFileSystem(ClientSession session) throws IOException {
+    default SftpFileSystem createSftpFileSystem(ClientSession session) throws IOException {
         return createSftpFileSystem(session, SftpVersionSelector.CURRENT);
     }
 
-    default FileSystem createSftpFileSystem(ClientSession session, int version) throws IOException {
+    default SftpFileSystem createSftpFileSystem(ClientSession session, int version) throws IOException {
         return createSftpFileSystem(session, SftpVersionSelector.fixedVersionSelector(version));
     }
 
-    default FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
+    default SftpFileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
         return createSftpFileSystem(session, selector, SftpClient.DEFAULT_READ_BUFFER_SIZE, SftpClient.DEFAULT_WRITE_BUFFER_SIZE);
     }
 
-    default FileSystem createSftpFileSystem(ClientSession session, int version, int readBufferSize, int writeBufferSize) throws IOException {
+    default SftpFileSystem createSftpFileSystem(ClientSession session, int version, int readBufferSize, int writeBufferSize) throws IOException {
         return createSftpFileSystem(session, SftpVersionSelector.fixedVersionSelector(version), readBufferSize, writeBufferSize);
     }
 
-    default FileSystem createSftpFileSystem(ClientSession session, int readBufferSize, int writeBufferSize) throws IOException {
+    default SftpFileSystem createSftpFileSystem(ClientSession session, int readBufferSize, int writeBufferSize) throws IOException {
         return createSftpFileSystem(session, SftpVersionSelector.CURRENT, readBufferSize, writeBufferSize);
     }
 
@@ -92,10 +92,10 @@ public interface SftpClientFactory {
      * @param selector The {@link SftpVersionSelector} to use in order to negotiate the SFTP version
      * @param readBufferSize Default I/O read buffer size
      * @param writeBufferSize Default I/O write buffer size
-     * @return The created {@link FileSystem} instance
+     * @return The created {@link SftpFileSystem} instance
      * @throws IOException If failed to create the instance
      */
-    FileSystem createSftpFileSystem(
+    SftpFileSystem createSftpFileSystem(
         ClientSession session, SftpVersionSelector selector, int readBufferSize, int writeBufferSize)
             throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirectoryStream.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirectoryStream.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirectoryStream.java
deleted file mode 100644
index 5f48966..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirectoryStream.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Path;
-import java.util.Iterator;
-
-/**
- * Implements a remote {@link DirectoryStream}
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpDirectoryStream implements DirectoryStream<Path> {
-    private final SftpClient sftp;
-    private final Iterable<SftpClient.DirEntry> iter;
-    private final SftpPath p;
-
-    /**
-     * @param path The remote {@link SftpPath}
-     * @throws IOException If failed to initialize the directory access handle
-     */
-    public SftpDirectoryStream(SftpPath path) throws IOException {
-        SftpFileSystem fs = path.getFileSystem();
-        p = path;
-        sftp = fs.getClient();
-        iter = sftp.readDir(path.toString());
-    }
-
-    /**
-     * Client instance used to access the remote directory
-     *
-     * @return The {@link SftpClient} instance used to access the remote directory
-     */
-    public final SftpClient getClient() {
-        return sftp;
-    }
-
-    @Override
-    public Iterator<Path> iterator() {
-        return new SftpPathIterator(p, iter);
-    }
-
-    @Override
-    public void close() throws IOException {
-        sftp.close();
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
deleted file mode 100644
index 8a6f1f1..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileStoreAttributeView;
-import java.util.Collection;
-
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpFileStore extends FileStore {
-    private final SftpFileSystem fs;
-    private final String name;
-
-    public SftpFileStore(String name, SftpFileSystem fs) {
-        this.name = name;
-        this.fs = fs;
-    }
-
-    public final SftpFileSystem getFileSystem() {
-        return fs;
-    }
-
-    @Override
-    public String name() {
-        return name;
-    }
-
-    @Override
-    public String type() {
-        return SftpConstants.SFTP_SUBSYSTEM_NAME;
-    }
-
-    @Override
-    public boolean isReadOnly() {
-        return false;
-    }
-
-    @Override
-    public long getTotalSpace() throws IOException {
-        return Long.MAX_VALUE;  // TODO use SFTPv6 space-available extension
-    }
-
-    @Override
-    public long getUsableSpace() throws IOException {
-        return Long.MAX_VALUE;
-    }
-
-    @Override
-    public long getUnallocatedSpace() throws IOException {
-        return Long.MAX_VALUE;
-    }
-
-    @Override
-    public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
-        SftpFileSystem sftpFs = getFileSystem();
-        SftpFileSystemProvider provider = sftpFs.provider();
-        return provider.isSupportedFileAttributeView(sftpFs, type);
-    }
-
-    @Override
-    public boolean supportsFileAttributeView(String name) {
-        if (GenericUtils.isEmpty(name)) {
-            return false;   // debug breakpoint
-        }
-
-        FileSystem sftpFs = getFileSystem();
-        Collection<String> views = sftpFs.supportedFileAttributeViews();
-        return !GenericUtils.isEmpty(views) && views.contains(name);
-    }
-
-    @Override
-    public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
-        return null;    // no special views supported
-    }
-
-    @Override
-    public Object getAttribute(String attribute) throws IOException {
-        return null;    // no special attributes supported
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
deleted file mode 100644
index ed16a26..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
+++ /dev/null
@@ -1,600 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.StreamCorruptedException;
-import java.nio.charset.Charset;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystemException;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.NavigableMap;
-import java.util.NavigableSet;
-import java.util.Objects;
-import java.util.Queue;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.channel.ClientChannel;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.session.ClientSessionHolder;
-import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpClient;
-import org.apache.sshd.common.file.util.BaseFileSystem;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-
-public class SftpFileSystem extends BaseFileSystem<SftpPath> implements ClientSessionHolder {
-    public static final String POOL_SIZE_PROP = "sftp-fs-pool-size";
-    public static final int DEFAULT_POOL_SIZE = 8;
-
-    public static final NavigableSet<String> UNIVERSAL_SUPPORTED_VIEWS =
-        Collections.unmodifiableNavigableSet(
-            GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, "basic", "posix", "owner"));
-
-    private final String id;
-    private final ClientSession clientSession;
-    private final SftpClientFactory factory;
-    private final SftpVersionSelector selector;
-    private final Queue<SftpClient> pool;
-    private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
-    private final int version;
-    private final Set<String> supportedViews;
-    private SftpPath defaultDir;
-    private int readBufferSize = SftpClient.DEFAULT_READ_BUFFER_SIZE;
-    private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
-    private final List<FileStore> stores;
-
-    public SftpFileSystem(
-            SftpFileSystemProvider provider, String id, ClientSession session,
-            SftpClientFactory factory, SftpVersionSelector selector)
-                throws IOException {
-        super(provider);
-        this.id = id;
-        this.clientSession = Objects.requireNonNull(session, "No client session");
-        this.factory = factory != null ? factory : SftpClientFactory.instance();
-        this.selector = selector;
-        this.stores = Collections.unmodifiableList(Collections.<FileStore>singletonList(new SftpFileStore(id, this)));
-        this.pool = new LinkedBlockingQueue<>(session.getIntProperty(POOL_SIZE_PROP, DEFAULT_POOL_SIZE));
-        try (SftpClient client = getClient()) {
-            version = client.getVersion();
-            defaultDir = getPath(client.canonicalPath("."));
-        }
-
-        if (version >= SftpConstants.SFTP_V4) {
-            Set<String> views = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
-            views.addAll(UNIVERSAL_SUPPORTED_VIEWS);
-            views.add("acl");
-            supportedViews = Collections.unmodifiableSet(views);
-        } else {
-            supportedViews = UNIVERSAL_SUPPORTED_VIEWS;
-        }
-    }
-
-    public final SftpVersionSelector getSftpVersionSelector() {
-        return selector;
-    }
-
-    public final String getId() {
-        return id;
-    }
-
-    public final int getVersion() {
-        return version;
-    }
-
-    @Override
-    public SftpFileSystemProvider provider() {
-        return (SftpFileSystemProvider) super.provider();
-    }
-
-    @Override   // NOTE: co-variant return
-    public List<FileStore> getFileStores() {
-        return this.stores;
-    }
-
-    public int getReadBufferSize() {
-        return readBufferSize;
-    }
-
-    public void setReadBufferSize(int size) {
-        if (size < SftpClient.MIN_READ_BUFFER_SIZE) {
-            throw new IllegalArgumentException("Insufficient read buffer size: " + size + ", min.=" + SftpClient.MIN_READ_BUFFER_SIZE);
-        }
-
-        readBufferSize = size;
-    }
-
-    public int getWriteBufferSize() {
-        return writeBufferSize;
-    }
-
-    public void setWriteBufferSize(int size) {
-        if (size < SftpClient.MIN_WRITE_BUFFER_SIZE) {
-            throw new IllegalArgumentException("Insufficient write buffer size: " + size + ", min.=" + SftpClient.MIN_WRITE_BUFFER_SIZE);
-        }
-
-        writeBufferSize = size;
-    }
-
-    @Override
-    protected SftpPath create(String root, List<String> names) {
-        return new SftpPath(this, root, names);
-    }
-
-    @Override
-    public ClientSession getClientSession() {
-        return clientSession;
-    }
-
-    @SuppressWarnings("synthetic-access")
-    public SftpClient getClient() throws IOException {
-        Wrapper wrapper = wrappers.get();
-        if (wrapper == null) {
-            while (wrapper == null) {
-                SftpClient client = pool.poll();
-                if (client == null) {
-                    ClientSession session = getClientSession();
-                    client = factory.createSftpClient(session, getSftpVersionSelector());
-                }
-                if (!client.isClosing()) {
-                    wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
-                }
-            }
-            wrappers.set(wrapper);
-        } else {
-            wrapper.increment();
-        }
-        return wrapper;
-    }
-
-    @Override
-    public void close() throws IOException {
-        if (isOpen()) {
-            SftpFileSystemProvider provider = provider();
-            String fsId = getId();
-            SftpFileSystem fs = provider.removeFileSystem(fsId);
-            ClientSession session = getClientSession();
-            session.close(true);
-
-            if ((fs != null) && (fs != this)) {
-                throw new FileSystemException(fsId, fsId, "Mismatched FS instance for id=" + fsId);
-            }
-        }
-    }
-
-    @Override
-    public boolean isOpen() {
-        ClientSession session = getClientSession();
-        return session.isOpen();
-    }
-
-    @Override
-    public Set<String> supportedFileAttributeViews() {
-        return supportedViews;
-    }
-
-    @Override
-    public UserPrincipalLookupService getUserPrincipalLookupService() {
-        return DefaultUserPrincipalLookupService.INSTANCE;
-    }
-
-    @Override
-    public SftpPath getDefaultDir() {
-        return defaultDir;
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getSimpleName() + "[" + getClientSession() + "]";
-    }
-
-    private final class Wrapper extends AbstractSftpClient {
-        private final SftpClient delegate;
-        private final AtomicInteger count = new AtomicInteger(1);
-        private final int readSize;
-        private final int writeSize;
-
-        private Wrapper(SftpClient delegate, int readSize, int writeSize) {
-            this.delegate = delegate;
-            this.readSize = readSize;
-            this.writeSize = writeSize;
-        }
-
-        @Override
-        public int getVersion() {
-            return delegate.getVersion();
-        }
-
-        @Override
-        public ClientSession getClientSession() {
-            return delegate.getClientSession();
-        }
-
-        @Override
-        public ClientChannel getClientChannel() {
-            return delegate.getClientChannel();
-        }
-
-        @Override
-        public NavigableMap<String, byte[]> getServerExtensions() {
-            return delegate.getServerExtensions();
-        }
-
-        @Override
-        public Charset getNameDecodingCharset() {
-            return delegate.getNameDecodingCharset();
-        }
-
-        @Override
-        public void setNameDecodingCharset(Charset cs) {
-            delegate.setNameDecodingCharset(cs);
-        }
-
-        @Override
-        public boolean isClosing() {
-            return false;
-        }
-
-        @Override
-        public boolean isOpen() {
-            return count.get() > 0;
-        }
-
-        @SuppressWarnings("synthetic-access")
-        @Override
-        public void close() throws IOException {
-            if (count.decrementAndGet() <= 0) {
-                if (!pool.offer(delegate)) {
-                    delegate.close();
-                }
-                wrappers.set(null);
-            }
-        }
-
-        public void increment() {
-            count.incrementAndGet();
-        }
-
-        @Override
-        public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("open(" + path + ")[" + options + "] client is closed");
-            }
-            return delegate.open(path, options);
-        }
-
-        @Override
-        public void close(Handle handle) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("close(" + handle + ") client is closed");
-            }
-            delegate.close(handle);
-        }
-
-        @Override
-        public void remove(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("remove(" + path + ") client is closed");
-            }
-            delegate.remove(path);
-        }
-
-        @Override
-        public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
-            }
-            delegate.rename(oldPath, newPath, options);
-        }
-
-        @Override
-        public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
-            }
-            return delegate.read(handle, fileOffset, dst, dstOffset, len);
-        }
-
-        @Override
-        public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
-            }
-            delegate.write(handle, fileOffset, src, srcOffset, len);
-        }
-
-        @Override
-        public void mkdir(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("mkdir(" + path + ") client is closed");
-            }
-            delegate.mkdir(path);
-        }
-
-        @Override
-        public void rmdir(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("rmdir(" + path + ") client is closed");
-            }
-            delegate.rmdir(path);
-        }
-
-        @Override
-        public CloseableHandle openDir(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("openDir(" + path + ") client is closed");
-            }
-            return delegate.openDir(path);
-        }
-
-        @Override
-        public List<DirEntry> readDir(Handle handle) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("readDir(" + handle + ") client is closed");
-            }
-            return delegate.readDir(handle);
-        }
-
-        @Override
-        public Iterable<DirEntry> listDir(Handle handle) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("readDir(" + handle + ") client is closed");
-            }
-            return delegate.listDir(handle);
-        }
-
-        @Override
-        public String canonicalPath(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("canonicalPath(" + path + ") client is closed");
-            }
-            return delegate.canonicalPath(path);
-        }
-
-        @Override
-        public Attributes stat(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("stat(" + path + ") client is closed");
-            }
-            return delegate.stat(path);
-        }
-
-        @Override
-        public Attributes lstat(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("lstat(" + path + ") client is closed");
-            }
-            return delegate.lstat(path);
-        }
-
-        @Override
-        public Attributes stat(Handle handle) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("stat(" + handle + ") client is closed");
-            }
-            return delegate.stat(handle);
-        }
-
-        @Override
-        public void setStat(String path, Attributes attributes) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed");
-            }
-            delegate.setStat(path, attributes);
-        }
-
-        @Override
-        public void setStat(Handle handle, Attributes attributes) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
-            }
-            delegate.setStat(handle, attributes);
-        }
-
-        @Override
-        public String readLink(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("readLink(" + path + ") client is closed");
-            }
-            return delegate.readLink(path);
-        }
-
-        @Override
-        public void symLink(String linkPath, String targetPath) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("symLink(" + linkPath + " => " + targetPath + ") client is closed");
-            }
-            delegate.symLink(linkPath, targetPath);
-        }
-
-        @Override
-        public Iterable<DirEntry> readDir(String path) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("readDir(" + path + ") client is closed");
-            }
-            return delegate.readDir(path);
-        }
-
-        @Override
-        public InputStream read(String path) throws IOException {
-            return read(path, readSize);
-        }
-
-        @Override
-        public InputStream read(String path, OpenMode... mode) throws IOException {
-            return read(path, readSize, mode);
-        }
-
-        @Override
-        public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
-            return read(path, readSize, mode);
-        }
-
-        @Override
-        public InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("read(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
-            }
-            return delegate.read(path, bufferSize, mode);
-        }
-
-        @Override
-        public OutputStream write(String path) throws IOException {
-            return write(path, writeSize);
-        }
-
-        @Override
-        public OutputStream write(String path, OpenMode... mode) throws IOException {
-            return write(path, writeSize, mode);
-        }
-
-        @Override
-        public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
-            return write(path, writeSize, mode);
-        }
-
-        @Override
-        public OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
-            }
-            return delegate.write(path, bufferSize, mode);
-        }
-
-        @Override
-        public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("link(" + linkPath + " => " + targetPath + "] symbolic=" + symbolic + ": client is closed");
-            }
-            delegate.link(linkPath, targetPath, symbolic);
-        }
-
-        @Override
-        public void lock(Handle handle, long offset, long length, int mask) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
-            }
-            delegate.lock(handle, offset, length, mask);
-        }
-
-        @Override
-        public void unlock(Handle handle, long offset, long length) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
-            }
-            delegate.unlock(handle, offset, length);
-        }
-
-        @Override
-        public int send(int cmd, Buffer buffer) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("send(cmd=" + SftpConstants.getCommandMessageName(cmd) + ") client is closed");
-            }
-
-            if (delegate instanceof RawSftpClient) {
-                return ((RawSftpClient) delegate).send(cmd, buffer);
-            } else {
-                throw new StreamCorruptedException("send(cmd=" + SftpConstants.getCommandMessageName(cmd) + ") delegate is not a " + RawSftpClient.class.getSimpleName());
-            }
-        }
-
-        @Override
-        public Buffer receive(int id) throws IOException {
-            if (!isOpen()) {
-                throw new IOException("receive(id=" + id + ") client is closed");
-            }
-
-            if (delegate instanceof RawSftpClient) {
-                return ((RawSftpClient) delegate).receive(id);
-            } else {
-                throw new StreamCorruptedException("receive(id=" + id + ") delegate is not a " + RawSftpClient.class.getSimpleName());
-            }
-        }
-    }
-
-    public static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
-        public static final DefaultUserPrincipalLookupService INSTANCE = new DefaultUserPrincipalLookupService();
-
-        public DefaultUserPrincipalLookupService() {
-            super();
-        }
-
-        @Override
-        public UserPrincipal lookupPrincipalByName(String name) throws IOException {
-            return new DefaultUserPrincipal(name);
-        }
-
-        @Override
-        public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
-            return new DefaultGroupPrincipal(group);
-        }
-    }
-
-    public static class DefaultUserPrincipal implements UserPrincipal {
-
-        private final String name;
-
-        public DefaultUserPrincipal(String name) {
-            this.name = Objects.requireNonNull(name, "name is null");
-        }
-
-        @Override
-        public final String getName() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            DefaultUserPrincipal that = (DefaultUserPrincipal) o;
-            return Objects.equals(this.getName(), that.getName());
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(getName());
-        }
-
-        @Override
-        public String toString() {
-            return getName();
-        }
-    }
-
-    public static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {
-        public DefaultGroupPrincipal(String name) {
-            super(name);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemChannel.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemChannel.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemChannel.java
deleted file mode 100644
index 40948bf..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemChannel.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Objects;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpFileSystemChannel extends SftpRemotePathChannel {
-    public SftpFileSystemChannel(SftpPath p, Collection<SftpClient.OpenMode> modes) throws IOException {
-        this(Objects.requireNonNull(p, "No target path").toString(), p.getFileSystem(), modes);
-    }
-
-    public SftpFileSystemChannel(String remotePath, SftpFileSystem fs, Collection<SftpClient.OpenMode> modes) throws IOException {
-        super(remotePath, Objects.requireNonNull(fs, "No SFTP file system").getClient(), true, modes);
-    }
-}


[5/8] mina-sshd git commit: [SSHD-861] Added SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystem.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystem.java
new file mode 100644
index 0000000..1602e9f
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystem.java
@@ -0,0 +1,604 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.nio.charset.Charset;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystemException;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionHolder;
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpClient;
+import org.apache.sshd.common.file.util.BaseFileSystem;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+public class SftpFileSystem extends BaseFileSystem<SftpPath> implements ClientSessionHolder {
+    public static final String POOL_SIZE_PROP = "sftp-fs-pool-size";
+    public static final int DEFAULT_POOL_SIZE = 8;
+
+    public static final NavigableSet<String> UNIVERSAL_SUPPORTED_VIEWS =
+        Collections.unmodifiableNavigableSet(
+            GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, "basic", "posix", "owner"));
+
+    private final String id;
+    private final ClientSession clientSession;
+    private final SftpClientFactory factory;
+    private final SftpVersionSelector selector;
+    private final Queue<SftpClient> pool;
+    private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
+    private final int version;
+    private final Set<String> supportedViews;
+    private SftpPath defaultDir;
+    private int readBufferSize = SftpClient.DEFAULT_READ_BUFFER_SIZE;
+    private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
+    private final List<FileStore> stores;
+
+    public SftpFileSystem(
+            SftpFileSystemProvider provider, String id, ClientSession session,
+            SftpClientFactory factory, SftpVersionSelector selector)
+                throws IOException {
+        super(provider);
+        this.id = id;
+        this.clientSession = Objects.requireNonNull(session, "No client session");
+        this.factory = factory != null ? factory : SftpClientFactory.instance();
+        this.selector = selector;
+        this.stores = Collections.unmodifiableList(Collections.<FileStore>singletonList(new SftpFileStore(id, this)));
+        this.pool = new LinkedBlockingQueue<>(session.getIntProperty(POOL_SIZE_PROP, DEFAULT_POOL_SIZE));
+        try (SftpClient client = getClient()) {
+            version = client.getVersion();
+            defaultDir = getPath(client.canonicalPath("."));
+        }
+
+        if (version >= SftpConstants.SFTP_V4) {
+            Set<String> views = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+            views.addAll(UNIVERSAL_SUPPORTED_VIEWS);
+            views.add("acl");
+            supportedViews = Collections.unmodifiableSet(views);
+        } else {
+            supportedViews = UNIVERSAL_SUPPORTED_VIEWS;
+        }
+    }
+
+    public final SftpVersionSelector getSftpVersionSelector() {
+        return selector;
+    }
+
+    public final String getId() {
+        return id;
+    }
+
+    public final int getVersion() {
+        return version;
+    }
+
+    @Override
+    public SftpFileSystemProvider provider() {
+        return (SftpFileSystemProvider) super.provider();
+    }
+
+    @Override   // NOTE: co-variant return
+    public List<FileStore> getFileStores() {
+        return this.stores;
+    }
+
+    public int getReadBufferSize() {
+        return readBufferSize;
+    }
+
+    public void setReadBufferSize(int size) {
+        if (size < SftpClient.MIN_READ_BUFFER_SIZE) {
+            throw new IllegalArgumentException("Insufficient read buffer size: " + size + ", min.=" + SftpClient.MIN_READ_BUFFER_SIZE);
+        }
+
+        readBufferSize = size;
+    }
+
+    public int getWriteBufferSize() {
+        return writeBufferSize;
+    }
+
+    public void setWriteBufferSize(int size) {
+        if (size < SftpClient.MIN_WRITE_BUFFER_SIZE) {
+            throw new IllegalArgumentException("Insufficient write buffer size: " + size + ", min.=" + SftpClient.MIN_WRITE_BUFFER_SIZE);
+        }
+
+        writeBufferSize = size;
+    }
+
+    @Override
+    protected SftpPath create(String root, List<String> names) {
+        return new SftpPath(this, root, names);
+    }
+
+    @Override
+    public ClientSession getClientSession() {
+        return clientSession;
+    }
+
+    @SuppressWarnings("synthetic-access")
+    public SftpClient getClient() throws IOException {
+        Wrapper wrapper = wrappers.get();
+        if (wrapper == null) {
+            while (wrapper == null) {
+                SftpClient client = pool.poll();
+                if (client == null) {
+                    ClientSession session = getClientSession();
+                    client = factory.createSftpClient(session, getSftpVersionSelector());
+                }
+                if (!client.isClosing()) {
+                    wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
+                }
+            }
+            wrappers.set(wrapper);
+        } else {
+            wrapper.increment();
+        }
+        return wrapper;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (isOpen()) {
+            SftpFileSystemProvider provider = provider();
+            String fsId = getId();
+            SftpFileSystem fs = provider.removeFileSystem(fsId);
+            ClientSession session = getClientSession();
+            session.close(true);
+
+            if ((fs != null) && (fs != this)) {
+                throw new FileSystemException(fsId, fsId, "Mismatched FS instance for id=" + fsId);
+            }
+        }
+    }
+
+    @Override
+    public boolean isOpen() {
+        ClientSession session = getClientSession();
+        return session.isOpen();
+    }
+
+    @Override
+    public Set<String> supportedFileAttributeViews() {
+        return supportedViews;
+    }
+
+    @Override
+    public UserPrincipalLookupService getUserPrincipalLookupService() {
+        return DefaultUserPrincipalLookupService.INSTANCE;
+    }
+
+    @Override
+    public SftpPath getDefaultDir() {
+        return defaultDir;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + getClientSession() + "]";
+    }
+
+    private final class Wrapper extends AbstractSftpClient {
+        private final SftpClient delegate;
+        private final AtomicInteger count = new AtomicInteger(1);
+        private final int readSize;
+        private final int writeSize;
+
+        private Wrapper(SftpClient delegate, int readSize, int writeSize) {
+            this.delegate = delegate;
+            this.readSize = readSize;
+            this.writeSize = writeSize;
+        }
+
+        @Override
+        public int getVersion() {
+            return delegate.getVersion();
+        }
+
+        @Override
+        public ClientSession getClientSession() {
+            return delegate.getClientSession();
+        }
+
+        @Override
+        public ClientChannel getClientChannel() {
+            return delegate.getClientChannel();
+        }
+
+        @Override
+        public NavigableMap<String, byte[]> getServerExtensions() {
+            return delegate.getServerExtensions();
+        }
+
+        @Override
+        public Charset getNameDecodingCharset() {
+            return delegate.getNameDecodingCharset();
+        }
+
+        @Override
+        public void setNameDecodingCharset(Charset cs) {
+            delegate.setNameDecodingCharset(cs);
+        }
+
+        @Override
+        public boolean isClosing() {
+            return false;
+        }
+
+        @Override
+        public boolean isOpen() {
+            return count.get() > 0;
+        }
+
+        @SuppressWarnings("synthetic-access")
+        @Override
+        public void close() throws IOException {
+            if (count.decrementAndGet() <= 0) {
+                if (!pool.offer(delegate)) {
+                    delegate.close();
+                }
+                wrappers.set(null);
+            }
+        }
+
+        public void increment() {
+            count.incrementAndGet();
+        }
+
+        @Override
+        public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("open(" + path + ")[" + options + "] client is closed");
+            }
+            return delegate.open(path, options);
+        }
+
+        @Override
+        public void close(Handle handle) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("close(" + handle + ") client is closed");
+            }
+            delegate.close(handle);
+        }
+
+        @Override
+        public void remove(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("remove(" + path + ") client is closed");
+            }
+            delegate.remove(path);
+        }
+
+        @Override
+        public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
+            }
+            delegate.rename(oldPath, newPath, options);
+        }
+
+        @Override
+        public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
+            }
+            return delegate.read(handle, fileOffset, dst, dstOffset, len);
+        }
+
+        @Override
+        public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
+            }
+            delegate.write(handle, fileOffset, src, srcOffset, len);
+        }
+
+        @Override
+        public void mkdir(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("mkdir(" + path + ") client is closed");
+            }
+            delegate.mkdir(path);
+        }
+
+        @Override
+        public void rmdir(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("rmdir(" + path + ") client is closed");
+            }
+            delegate.rmdir(path);
+        }
+
+        @Override
+        public CloseableHandle openDir(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("openDir(" + path + ") client is closed");
+            }
+            return delegate.openDir(path);
+        }
+
+        @Override
+        public List<DirEntry> readDir(Handle handle) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("readDir(" + handle + ") client is closed");
+            }
+            return delegate.readDir(handle);
+        }
+
+        @Override
+        public Iterable<DirEntry> listDir(Handle handle) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("readDir(" + handle + ") client is closed");
+            }
+            return delegate.listDir(handle);
+        }
+
+        @Override
+        public String canonicalPath(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("canonicalPath(" + path + ") client is closed");
+            }
+            return delegate.canonicalPath(path);
+        }
+
+        @Override
+        public Attributes stat(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("stat(" + path + ") client is closed");
+            }
+            return delegate.stat(path);
+        }
+
+        @Override
+        public Attributes lstat(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("lstat(" + path + ") client is closed");
+            }
+            return delegate.lstat(path);
+        }
+
+        @Override
+        public Attributes stat(Handle handle) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("stat(" + handle + ") client is closed");
+            }
+            return delegate.stat(handle);
+        }
+
+        @Override
+        public void setStat(String path, Attributes attributes) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed");
+            }
+            delegate.setStat(path, attributes);
+        }
+
+        @Override
+        public void setStat(Handle handle, Attributes attributes) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
+            }
+            delegate.setStat(handle, attributes);
+        }
+
+        @Override
+        public String readLink(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("readLink(" + path + ") client is closed");
+            }
+            return delegate.readLink(path);
+        }
+
+        @Override
+        public void symLink(String linkPath, String targetPath) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("symLink(" + linkPath + " => " + targetPath + ") client is closed");
+            }
+            delegate.symLink(linkPath, targetPath);
+        }
+
+        @Override
+        public Iterable<DirEntry> readDir(String path) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("readDir(" + path + ") client is closed");
+            }
+            return delegate.readDir(path);
+        }
+
+        @Override
+        public InputStream read(String path) throws IOException {
+            return read(path, readSize);
+        }
+
+        @Override
+        public InputStream read(String path, OpenMode... mode) throws IOException {
+            return read(path, readSize, mode);
+        }
+
+        @Override
+        public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
+            return read(path, readSize, mode);
+        }
+
+        @Override
+        public InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("read(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
+            }
+            return delegate.read(path, bufferSize, mode);
+        }
+
+        @Override
+        public OutputStream write(String path) throws IOException {
+            return write(path, writeSize);
+        }
+
+        @Override
+        public OutputStream write(String path, OpenMode... mode) throws IOException {
+            return write(path, writeSize, mode);
+        }
+
+        @Override
+        public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
+            return write(path, writeSize, mode);
+        }
+
+        @Override
+        public OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
+            }
+            return delegate.write(path, bufferSize, mode);
+        }
+
+        @Override
+        public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("link(" + linkPath + " => " + targetPath + "] symbolic=" + symbolic + ": client is closed");
+            }
+            delegate.link(linkPath, targetPath, symbolic);
+        }
+
+        @Override
+        public void lock(Handle handle, long offset, long length, int mask) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
+            }
+            delegate.lock(handle, offset, length, mask);
+        }
+
+        @Override
+        public void unlock(Handle handle, long offset, long length) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
+            }
+            delegate.unlock(handle, offset, length);
+        }
+
+        @Override
+        public int send(int cmd, Buffer buffer) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("send(cmd=" + SftpConstants.getCommandMessageName(cmd) + ") client is closed");
+            }
+
+            if (delegate instanceof RawSftpClient) {
+                return ((RawSftpClient) delegate).send(cmd, buffer);
+            } else {
+                throw new StreamCorruptedException("send(cmd=" + SftpConstants.getCommandMessageName(cmd) + ") delegate is not a " + RawSftpClient.class.getSimpleName());
+            }
+        }
+
+        @Override
+        public Buffer receive(int id) throws IOException {
+            if (!isOpen()) {
+                throw new IOException("receive(id=" + id + ") client is closed");
+            }
+
+            if (delegate instanceof RawSftpClient) {
+                return ((RawSftpClient) delegate).receive(id);
+            } else {
+                throw new StreamCorruptedException("receive(id=" + id + ") delegate is not a " + RawSftpClient.class.getSimpleName());
+            }
+        }
+    }
+
+    public static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
+        public static final DefaultUserPrincipalLookupService INSTANCE = new DefaultUserPrincipalLookupService();
+
+        public DefaultUserPrincipalLookupService() {
+            super();
+        }
+
+        @Override
+        public UserPrincipal lookupPrincipalByName(String name) throws IOException {
+            return new DefaultUserPrincipal(name);
+        }
+
+        @Override
+        public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
+            return new DefaultGroupPrincipal(group);
+        }
+    }
+
+    public static class DefaultUserPrincipal implements UserPrincipal {
+
+        private final String name;
+
+        public DefaultUserPrincipal(String name) {
+            this.name = Objects.requireNonNull(name, "name is null");
+        }
+
+        @Override
+        public final String getName() {
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            DefaultUserPrincipal that = (DefaultUserPrincipal) o;
+            return Objects.equals(this.getName(), that.getName());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(getName());
+        }
+
+        @Override
+        public String toString() {
+            return getName();
+        }
+    }
+
+    public static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {
+        public DefaultGroupPrincipal(String name) {
+            super(name);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemChannel.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemChannel.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemChannel.java
new file mode 100644
index 0000000..6677a0e
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemChannel.java
@@ -0,0 +1,40 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpRemotePathChannel;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileSystemChannel extends SftpRemotePathChannel {
+    public SftpFileSystemChannel(SftpPath p, Collection<SftpClient.OpenMode> modes) throws IOException {
+        this(Objects.requireNonNull(p, "No target path").toString(), p.getFileSystem(), modes);
+    }
+
+    public SftpFileSystemChannel(String remotePath, SftpFileSystem fs, Collection<SftpClient.OpenMode> modes) throws IOException {
+        super(remotePath, Objects.requireNonNull(fs, "No SFTP file system").getClient(), true, modes);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
new file mode 100644
index 0000000..576e302
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
@@ -0,0 +1,97 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionCreator;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.common.auth.PasswordHolder;
+import org.apache.sshd.common.auth.UsernameHolder;
+
+/**
+ * Provides user hooks into the process of creating a {@link SftpFileSystem} via a {@link SftpFileSystemProvider}
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpFileSystemClientSessionInitializer {
+    SftpFileSystemClientSessionInitializer DEFAULT = new SftpFileSystemClientSessionInitializer() {
+        @Override
+        public String toString() {
+            return SftpFileSystemClientSessionInitializer.class.getSimpleName() + "[DEFAULT]";
+        }
+    };
+
+    /**
+     * Invoked by the {@link SftpFileSystemProvider#newFileSystem(java.net.URI, Map)} method
+     * in order to obtain an initial (non-authenticated) {@link ClientSession}.
+     *
+     * @param provider The {@link SftpFileSystemProvider} instance requesting the session
+     * @param context The initialization {@link SftpFileSystemInitializationContext}
+     * @return The created {@link ClientSession}
+     * @throws IOException If failed to connect
+     */
+    default ClientSession createClientSession(
+            SftpFileSystemProvider provider, SftpFileSystemInitializationContext context)
+                throws IOException {
+        UsernameHolder user = context.getCredentials();
+        ClientSessionCreator client = provider.getClientInstance();
+        return client.connect(user.getUsername(), context.getHost(), context.getPort())
+                .verify(context.getMaxConnectTime())
+                .getSession();
+    }
+
+    /**
+     * Invoked by the {@link SftpFileSystemProvider#newFileSystem(java.net.URI, Map)} method
+     * in order to authenticate the session obtained from
+     * {@link #createClientSession(SftpFileSystemProvider, SftpFileSystemInitializationContext)}
+     *
+     * @param provider The {@link SftpFileSystemProvider} instance requesting the session
+     * @param context The initialization {@link SftpFileSystemInitializationContext}
+     * @param session The created {@link ClientSession}
+     * @throws IOException If failed to authenticate
+     */
+    default void authenticateClientSession(
+            SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
+                throws IOException {
+        PasswordHolder password = context.getCredentials();
+        session.addPasswordIdentity(password.getPassword());
+        session.auth().verify(context.getMaxAuthTime());
+    }
+
+    /**
+     * Invoked by the {@link SftpFileSystemProvider#newFileSystem(java.net.URI, Map)} method
+     * in order to create the {@link SftpFileSystem} once session has been authenticated.
+     *
+     * @param provider The {@link SftpFileSystemProvider} instance requesting the session
+     * @param context The initialization {@link SftpFileSystemInitializationContext}
+     * @param session The authenticated {@link ClientSession}
+     * @param selector The <U>resolved</U> {@link SftpVersionSelector} to use
+     * @return The created {@link SftpFileSystem}
+     * @throws IOException If failed to create the file-system
+     */
+    default SftpFileSystem createSftpFileSystem(
+            SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session, SftpVersionSelector selector)
+                throws IOException {
+        return new SftpFileSystem(provider, context.getId(), session, provider.getSftpClientFactory(), selector);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemInitializationContext.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemInitializationContext.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemInitializationContext.java
new file mode 100644
index 0000000..b897620
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemInitializationContext.java
@@ -0,0 +1,142 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileSystemInitializationContext {
+    private final String id;
+    private final URI uri;
+    private final Map<String, ?> environment;
+    private String host;
+    private int port;
+    private BasicCredentialsProvider credentials;
+    private PropertyResolver propertyResolver;
+    private long maxConnectTime;
+    private long maxAuthTime;
+
+    /**
+     * @param id The unique identifier assigned to the file-system being created
+     * @param uri The original {@link URI} that triggered the file-system creation
+     * @param env The environment settings passed along with the URI (may be {@code null})
+     */
+    public SftpFileSystemInitializationContext(String id, URI uri, Map<String, ?> env) {
+        this.id = id;
+        this.uri = uri;
+        this.environment = env;
+    }
+
+    /**
+     * @return The unique identifier assigned to the file-system being created
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * @return The original {@link URI} that triggered the file-system creation
+     */
+    public URI getUri() {
+        return uri;
+    }
+
+    /**
+     * @return The environment settings passed along with the URI (may be {@code null})
+     */
+    public Map<String, ?> getEnvironment() {
+        return environment;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    /**
+     * @return The <U>resolved</U> target port from the URI
+     */
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    /**
+     * @return The credentials recovered from the URI
+     */
+    public BasicCredentialsProvider getCredentials() {
+        return credentials;
+    }
+
+    public void setCredentials(BasicCredentialsProvider credentials) {
+        this.credentials = credentials;
+    }
+
+    /**
+     * @return A {@link PropertyResolver} for easy access of any query parameters
+     * encoded in the URI
+     */
+    public PropertyResolver getPropertyResolver() {
+        return propertyResolver;
+    }
+
+    public void setPropertyResolver(PropertyResolver propertyResolver) {
+        this.propertyResolver = propertyResolver;
+    }
+
+    /**
+     * @return The <U>resolved</U> max. connect timeout (msec.)
+     */
+    public long getMaxConnectTime() {
+        return maxConnectTime;
+    }
+
+    public void setMaxConnectTime(long maxConnectTime) {
+        this.maxConnectTime = maxConnectTime;
+    }
+
+    /**
+     * @return The <U>resolved</U> max. authentication timeout (msec.)
+     */
+    public long getMaxAuthTime() {
+        return maxAuthTime;
+    }
+
+    public void setMaxAuthTime(long maxAuthTime) {
+        this.maxAuthTime = maxAuthTime;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + getId() + "]";
+    }
+}


[8/8] mina-sshd git commit: [SSHD-861] Fixed bug in NumberUtils#isIntegerNumber

Posted by lg...@apache.org.
[SSHD-861] Fixed bug in NumberUtils#isIntegerNumber


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/005ee40c
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/005ee40c
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/005ee40c

Branch: refs/heads/master
Commit: 005ee40c0ac8deb148e7e11fe772d4a449be4c0c
Parents: c64bd7a
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 11 08:33:56 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 11 18:56:28 2018 +0200

----------------------------------------------------------------------
 .../apache/sshd/common/util/NumberUtils.java    |  4 ++--
 .../sshd/common/util/NumberUtilsTest.java       | 20 ++++++++++++++++++++
 2 files changed, 22 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/005ee40c/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
index c8e1817..444113f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
@@ -290,8 +290,8 @@ public final class NumberUtils {
             return false;
         }
 
-        for (int index = 0; index < cs.length(); index++) {
-            char c = cs.charAt(0);
+        for (int index = 0, len = cs.length(); index < len; index++) {
+            char c = cs.charAt(index);
             if ((c >= '0') && (c <= '9')) {
                 continue;
             }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/005ee40c/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java
index 927cecb..d7c2d32 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java
@@ -74,4 +74,24 @@ public class NumberUtilsTest extends JUnitTestSupport {
             }
         }
     }
+
+    @Test
+    public void testIsValidIntegerNumber() {
+        for (String s : new String[]{"7", "73", "736", "7365", "19650307"}) {
+            assertTrue(s, NumberUtils.isIntegerNumber(s));
+
+            String pos = "+" + s;
+            assertTrue(pos, NumberUtils.isIntegerNumber(pos));
+
+            String neg = "-" + s;
+            assertTrue(neg, NumberUtils.isIntegerNumber(neg));
+        }
+    }
+
+    @Test
+    public void testIsInvalidIntegerNumber() {
+        for (String s : new String[]{null, "", "    ", getCurrentTestName(), "3rd", "3.14", "-.3"}) {
+            assertFalse(s, NumberUtils.isIntegerNumber(s));
+        }
+    }
 }


[2/8] mina-sshd git commit: [SSHD-861] Handle correctly special characters in username/password when creating an SFTP file system URI

Posted by lg...@apache.org.
[SSHD-861] Handle correctly special characters in username/password when creating an SFTP file system URI


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/c24635d5
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/c24635d5
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/c24635d5

Branch: refs/heads/master
Commit: c24635d5cb86a106f5ca33f22e2a1b39d7aad512
Parents: 005ee40
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 11 08:59:46 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 11 18:56:28 2018 +0200

----------------------------------------------------------------------
 .../sshd/common/auth/BasicCredentialsImpl.java  |  92 ++++++++++++++
 .../common/auth/BasicCredentialsProvider.java   |  27 +++++
 .../common/auth/MutableBasicCredentials.java    |  27 +++++
 .../sshd/common/auth/MutablePassword.java       |  27 +++++
 .../apache/sshd/common/auth/PasswordHolder.java |  28 +++++
 .../loader/PrivateKeyEncryptionContext.java     |   5 +-
 .../apache/sshd/util/test/JUnitTestSupport.java |  22 +++-
 .../apache/sshd/util/test/SimpleUserInfo.java   |   3 +-
 .../subsystem/sftp/SftpFileSystemProvider.java  |  92 +++++++++++---
 .../subsystem/sftp/SftpFileSystemURITest.java   | 121 +++++++++++++++++++
 .../sftp/ApacheSshdSftpSessionFactory.java      |  15 ++-
 .../sftp/ApacheSshdSftpSessionFactoryTest.java  |   2 +-
 12 files changed, 432 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java
new file mode 100644
index 0000000..4e444f0
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java
@@ -0,0 +1,92 @@
+/*
+ * 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.common.auth;
+
+import java.util.Objects;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class BasicCredentialsImpl implements MutableBasicCredentials, Cloneable {
+    private String username;
+    private String password;
+
+    public BasicCredentialsImpl() {
+        super();
+    }
+
+    public BasicCredentialsImpl(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    @Override
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    @Override
+    public String getPassword() {
+        return password;
+    }
+
+    @Override
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    @Override
+    public BasicCredentialsImpl clone() {
+        try {
+            return getClass().cast(super.clone());
+        } catch (CloneNotSupportedException e) {
+            throw new UnsupportedOperationException("Unexpected failure to clone: " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getUsername(), getPassword());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        BasicCredentialsImpl other = (BasicCredentialsImpl) obj;
+        return Objects.equals(getUsername(), other.getUsername())
+            && Objects.equals(getPassword(), other.getPassword());
+    }
+
+    // NOTE: do not implement 'toString' on purpose to avoid inadvertent logging of contents
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java
new file mode 100644
index 0000000..7ddde04
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.common.auth;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface BasicCredentialsProvider extends UsernameHolder, PasswordHolder {
+    // Nothing extra
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java
new file mode 100644
index 0000000..a7bb1d5
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java
@@ -0,0 +1,27 @@
+/*
+ * 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.common.auth;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface MutableBasicCredentials extends BasicCredentialsProvider, MutableUserHolder, MutablePassword {
+    // Nothing extra
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java
new file mode 100644
index 0000000..07e4843
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java
@@ -0,0 +1,27 @@
+/*
+ * 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.common.auth;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface MutablePassword extends PasswordHolder {
+    void setPassword(String password);
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java
new file mode 100644
index 0000000..5f2eff7
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java
@@ -0,0 +1,28 @@
+/*
+ * 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.common.auth;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface PasswordHolder {
+    String getPassword();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
index 5e03cdb..5303d57 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
@@ -30,13 +30,14 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.apache.sshd.common.auth.MutablePassword;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class PrivateKeyEncryptionContext implements Cloneable {
+public class PrivateKeyEncryptionContext implements MutablePassword, Cloneable {
     public static final String  DEFAULT_CIPHER_MODE = "CBC";
 
     private static final Map<String, PrivateKeyObfuscator> OBFUSCATORS =
@@ -82,10 +83,12 @@ public class PrivateKeyEncryptionContext implements Cloneable {
         cipherMode = value;
     }
 
+    @Override
     public String getPassword() {
         return password;
     }
 
+    @Override
     public void setPassword(String value) {
         password = value;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 01ba808..175cd93 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -51,6 +51,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
+import java.util.function.BiPredicate;
 
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
@@ -360,6 +361,11 @@ public abstract class JUnitTestSupport extends Assert {
     }
 
     public static <E> void assertListEquals(String message, List<? extends E> expected, List<? extends E> actual) {
+        assertListEquals(message, expected, actual, Objects::equals);
+    }
+
+    public static <E> void assertListEquals(
+            String message, List<? extends E> expected, List<? extends E> actual, BiPredicate<? super E, ? super E> equator) {
         int expSize = GenericUtils.size(expected);
         int actSize = GenericUtils.size(actual);
         assertEquals(message + "[size]", expSize, actSize);
@@ -367,18 +373,28 @@ public abstract class JUnitTestSupport extends Assert {
         for (int index = 0; index < expSize; index++) {
             E expValue = expected.get(index);
             E actValue = actual.get(index);
-            assertEquals(message + "[" + index + "]", expValue, actValue);
+            if (!equator.test(expValue, actValue)) {
+                fail(message + "[" + index + "]: expected=" + expValue + ", actual=" + actValue);
+            }
         }
     }
 
-    public static <K, V> void assertMapEquals(String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual) {
+    public static <K, V> void assertMapEquals(
+            String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual) {
+        assertMapEquals(message, expected, actual, Objects::equals);
+    }
+
+    public static <K, V> void assertMapEquals(
+            String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual, BiPredicate<? super V, ? super V> equator) {
         int numItems = GenericUtils.size(expected);
         assertEquals(message + "[size]", numItems, GenericUtils.size(actual));
 
         if (numItems > 0) {
             expected.forEach((key, expValue) -> {
                 V actValue = actual.get(key);
-                assertEquals(message + "[" + key + "]", expValue, actValue);
+                if (!equator.test(expValue, actValue)) {
+                    fail(message + "[" + key + "]: expected=" + expValue + ", actual=" + actValue);
+                }
             });
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java b/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java
index 31f7c41..5062e8d 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java
@@ -65,7 +65,8 @@ public class SimpleUserInfo implements UserInfo, UIKeyboardInteractive {
     }
 
     @Override
-    public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
+    public String[] promptKeyboardInteractive(
+            String destination, String name, String instruction, String[] prompt, boolean[] echo) {
         return new String[]{password};
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index 3c171d0..f8cd75b 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -24,6 +24,7 @@ import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.channels.FileChannel;
 import java.nio.charset.Charset;
 import java.nio.file.AccessDeniedException;
@@ -75,6 +76,9 @@ import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.auth.BasicCredentialsImpl;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+import org.apache.sshd.common.auth.MutableBasicCredentials;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.subsystem.sftp.SftpException;
@@ -184,12 +188,9 @@ public class SftpFileSystemProvider extends FileSystemProvider {
             port = SshConstants.DEFAULT_PORT;
         }
 
-        String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided");
-        String[] ui = GenericUtils.split(userInfo, ':');
-        ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
-
-        String username = ui[0];
-        String password = ui[1];
+        BasicCredentialsProvider credentials = parseCredentials(uri);
+        ValidateUtils.checkState(credentials != null, "No credentials provided");
+        String username = credentials.getUsername();
         String id = getFileSystemIdentifier(host, port, username);
         Map<String, Object> params = resolveFileSystemParameters(env, parseURIParameters(uri));
         PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(params);
@@ -198,6 +199,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
             PropertyResolverUtils.getCharset(resolver, NAME_DECORDER_CHARSET_PROP_NAME, DEFAULT_NAME_DECODER_CHARSET);
         long maxConnectTime = resolver.getLongProperty(CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME);
         long maxAuthTime = resolver.getLongProperty(AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME);
+        String password = credentials.getPassword();
 
         SftpFileSystem fileSystem;
         synchronized (fileSystems) {
@@ -308,6 +310,27 @@ public class SftpFileSystemProvider extends FileSystemProvider {
         return resolved;
     }
 
+    /**
+     * Attempts to parse the user information from the URI
+     *
+     * @param uri The {@link URI} value - ignored if {@code null} or does not
+     * contain any {@link URI#getUserInfo() user info}.
+     * @return The parsed credentials - {@code null} if none available
+     */
+    public static MutableBasicCredentials parseCredentials(URI uri) {
+        return parseCredentials((uri == null) ? "" : uri.getUserInfo());
+    }
+
+    public static MutableBasicCredentials parseCredentials(String userInfo) {
+        if (GenericUtils.isEmpty(userInfo)) {
+            return null;
+        }
+
+        String[] ui = GenericUtils.split(userInfo, ':');
+        ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
+        return new BasicCredentialsImpl(ui[0], ui[1]);
+    }
+
     public static Map<String, Object> parseURIParameters(URI uri) {
         return parseURIParameters((uri == null) ? "" : uri.getQuery());
     }
@@ -336,7 +359,9 @@ public class SftpFileSystemProvider extends FileSystemProvider {
             String key = p.substring(0, pos);
             String value = p.substring(pos + 1);
             if (NumberUtils.isIntegerNumber(value)) {
-                map.put(key, Long.parseLong(value));
+                map.put(key, Long.valueOf(value));
+            } else if ("true".equals(value) || "false".equals("value")) {
+                map.put(key, Boolean.valueOf(value));
             } else {
                 map.put(key, value);
             }
@@ -1235,22 +1260,53 @@ public class SftpFileSystemProvider extends FileSystemProvider {
     }
 
     public static URI createFileSystemURI(String host, int port, String username, String password, Map<String, ?> params) {
-        StringBuilder sb = new StringBuilder(Byte.MAX_VALUE);
-        sb.append(SftpConstants.SFTP_SUBSYSTEM_NAME)
-            .append("://").append(username).append(':').append(password)
-            .append('@').append(host).append(':').append(port)
-            .append('/');
-        if (GenericUtils.size(params) > 0) {
-            boolean firstParam = true;
-            // Cannot use forEach because firstParam is not effectively final
+        ValidateUtils.checkNotNullAndNotEmpty(host, "No host provided");
+
+        String queryPart = null;
+        int numParams = GenericUtils.size(params);
+        if (numParams > 0) {
+            StringBuilder sb = new StringBuilder(numParams * Short.SIZE);
             for (Map.Entry<String, ?> pe : params.entrySet()) {
                 String key = pe.getKey();
                 Object value = pe.getValue();
-                sb.append(firstParam ? '?' : '&').append(key).append('=').append(Objects.toString(value, null));
-                firstParam = false;
+                if (sb.length() > 0) {
+                    sb.append('&');
+                }
+                sb.append(key);
+                if (value != null) {
+                    sb.append('=').append(Objects.toString(value, null));
+                }
             }
+
+            queryPart = sb.toString();
         }
 
-        return URI.create(sb.toString());
+        try {
+            String userAuth = encodeCredentials(username, password);
+            return new URI(SftpConstants.SFTP_SUBSYSTEM_NAME, userAuth, host, port, "/", queryPart, null);
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ")"
+                    + " to create access URI: " + e.getMessage(), e);
+        }
+    }
+
+    public static String encodeCredentials(String username, String password) {
+        ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided");
+        ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided");
+        /*
+         * There is no way to properly encode/decode credentials that already contain
+         * colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1:
+         *
+         *
+         *      Use of the format "user:password" in the userinfo field is
+         *      deprecated.  Applications should not render as clear text any data
+         *      after the first colon (":") character found within a userinfo
+         *      subcomponent unless the data after the colon is the empty string
+         *      (indicating no password).  Applications may choose to ignore or
+         *      reject such data when it is received as part of a reference and
+         *      should reject the storage of such data in unencrypted form.
+         */
+        ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials");
+        return username + ":" + password;
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
new file mode 100644
index 0000000..1e89e47
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@Category({ NoIoTestCase.class })
+public class SftpFileSystemURITest extends JUnitTestSupport {
+    private final String host;
+    private final int port;
+    private final String username;
+    private final String password;
+    private final Map<String, ?> params;
+
+    public SftpFileSystemURITest(String host, int port, String username, String password, Map<String, ?> params) {
+        this.host = host;
+        this.port = port;
+        this.username = username;
+        this.password = password;
+        this.params = params;
+    }
+
+    @Parameters(name = "host={0}, port={1}, user={2}, password={3}, params={4}")
+    public static List<Object[]> parameters() {
+        return new ArrayList<Object[]>() {
+            // Not serializing it
+            private static final long serialVersionUID = 1L;
+
+            {
+                add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, 0, "user", "password", null});
+                add(new Object[] {"37.77.34.7", 2222, "user", "password", Collections.singletonMap("non-default-port", true)});
+                add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, SshConstants.DEFAULT_PORT, "J@ck", "d@Ripper", new HashMap<String, Object>() {
+                        // not serializing it
+                        private static final long serialVersionUID = 1L;
+
+                        {
+                            put("param1", "1st");
+                            put("param2", 2);
+                            put("param3", false);
+                        }
+                    }
+                });
+                add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null});
+            }
+        };
+    }
+
+    @Test
+    public void testFullURIEncoding() {
+        URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password, params);
+        assertEquals("Mismatched scheme", SftpConstants.SFTP_SUBSYSTEM_NAME, uri.getScheme());
+        assertEquals("Mismatched host", host, uri.getHost());
+        assertEquals("Mismatched port", port, uri.getPort());
+
+        BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(uri);
+        assertNotNull("No credentials provided", credentials);
+        assertEquals("Mismatched user", username, credentials.getUsername());
+        assertEquals("Mismatched password", password, credentials.getPassword());
+
+        Map<String, ?> uriParams = SftpFileSystemProvider.parseURIParameters(uri);
+        assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString()));
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName()
+            + "[host=" + host
+            + ", port=" + port
+            + ", username=" + username
+            + ", password=" + password
+            + ", params=" + params
+            + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/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 dfa5855..3c0a65c 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
@@ -38,6 +38,7 @@ 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.SshConstants;
+import org.apache.sshd.common.auth.MutableBasicCredentials;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils;
@@ -62,7 +63,7 @@ import org.springframework.integration.file.remote.session.SharedSessionCapable;
 public class ApacheSshdSftpSessionFactory
         extends AbstractLoggingBean
         implements SessionFactory<DirEntry>, SharedSessionCapable,
-        SimpleClientConfigurator,
+        MutableBasicCredentials, SimpleClientConfigurator,
         InitializingBean, DisposableBean {
 
     // TODO add support for loading multiple private keys
@@ -119,19 +120,22 @@ public class ApacheSshdSftpSessionFactory
         this.portValue = port;
     }
 
-    public String getUser() {
+    @Override
+    public String getUsername() {
         return userValue;
     }
 
     /**
      * The remote user to use. This is a mandatory property.
      *
-     * @param user The username
+     * @param user The (never {@code null}/empty) username
      */
-    public void setUser(String user) {
+    @Override
+    public void setUsername(String user) {
         this.userValue = ValidateUtils.checkNotNullAndNotEmpty(user, "No user specified: %s", user);
     }
 
+    @Override
     public String getPassword() {
         return passwordValue;
     }
@@ -143,6 +147,7 @@ public class ApacheSshdSftpSessionFactory
      * @param password The password to use - if {@code null} then no password
      * is set - in which case the {@link #getPrivateKey()} resource is used
      */
+    @Override
     public void setPassword(String password) {
         this.passwordValue = password;
     }
@@ -396,7 +401,7 @@ public class ApacheSshdSftpSessionFactory
 
     protected ClientSession createClientSession() throws Exception {
         String hostname = ValidateUtils.checkNotNullAndNotEmpty(getHost(), "Host must not be empty");
-        String username = ValidateUtils.checkNotNullAndNotEmpty(getUser(), "User must not be empty");
+        String username = ValidateUtils.checkNotNullAndNotEmpty(getUsername(), "User must not be empty");
         String passwordIdentity = getPassword();
         KeyPair kp = getPrivateKeyPair();
         ValidateUtils.checkState(GenericUtils.isNotEmpty(passwordIdentity) || (kp != null),

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java
----------------------------------------------------------------------
diff --git a/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java b/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java
index 75d7195..4b85c20 100644
--- a/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java
+++ b/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java
@@ -388,7 +388,7 @@ public class ApacheSshdSftpSessionFactoryTest extends BaseTestSupport {
         ApacheSshdSftpSessionFactory factory = new ApacheSshdSftpSessionFactory(sharedSession);
         factory.setHost(TEST_LOCALHOST);
         factory.setPort(port);
-        factory.setUser(getCurrentTestName());
+        factory.setUsername(getCurrentTestName());
         factory.setPassword(getCurrentTestName());
         factory.setSshClient(client);
         factory.setConnectTimeout(TimeUnit.SECONDS.toMillis(7L));


[3/8] mina-sshd git commit: [SSHD-861] Added SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
deleted file mode 100644
index 9ecb690..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
+++ /dev/null
@@ -1,497 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.attribute.UserPrincipalNotFoundException;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.subsystem.sftp.SftpSubsystemEnvironment;
-import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
-import org.apache.sshd.util.test.BaseTestSupport;
-import org.apache.sshd.util.test.CommonTestSupportUtils;
-import org.apache.sshd.util.test.CoreTestSupportUtils;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@SuppressWarnings("checkstyle:MethodCount")
-public class SftpFileSystemTest extends BaseTestSupport {
-    private static SshServer sshd;
-    private static int port;
-
-    private final FileSystemFactory fileSystemFactory;
-
-    public SftpFileSystemTest() throws IOException {
-        Path targetPath = detectTargetFolder();
-        Path parentPath = targetPath.getParent();
-        fileSystemFactory = new VirtualFileSystemFactory(parentPath);
-    }
-
-    @BeforeClass
-    public static void setupServerInstance() throws Exception {
-        sshd = CoreTestSupportUtils.setupTestServer(SftpFileSystemTest.class);
-        sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
-        sshd.start();
-        port = sshd.getPort();
-    }
-
-    @AfterClass
-    public static void tearDownServerInstance() throws Exception {
-        if (sshd != null) {
-            try {
-                sshd.stop(true);
-            } finally {
-                sshd = null;
-            }
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        sshd.setFileSystemFactory(fileSystemFactory);
-    }
-
-    @Test
-    public void testFileSystem() throws Exception {
-        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
-                GenericUtils.<String, Object>mapBuilder()
-                        .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
-                        .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
-                        .build())) {
-            assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
-            testFileSystem(fs, ((SftpFileSystem) fs).getVersion());
-        }
-    }
-
-    @Test   // see SSHD-578
-    public void testFileSystemURIParameters() throws Exception {
-        Map<String, Object> params = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        params.put("test-class-name", getClass().getSimpleName());
-        params.put("test-pkg-name", getClass().getPackage().getName());
-        params.put("test-name", getCurrentTestName());
-
-        int expectedVersion = (SftpSubsystemEnvironment.LOWER_SFTP_IMPL + SftpSubsystemEnvironment.HIGHER_SFTP_IMPL) / 2;
-        params.put(SftpFileSystemProvider.VERSION_PARAM, expectedVersion);
-        try (SftpFileSystem fs = (SftpFileSystem) FileSystems.newFileSystem(createDefaultFileSystemURI(params), Collections.<String, Object>emptyMap())) {
-            try (SftpClient sftpClient = fs.getClient()) {
-                assertEquals("Mismatched negotiated version", expectedVersion, sftpClient.getVersion());
-
-                Session session = sftpClient.getClientSession();
-                params.forEach((key, expected) -> {
-                    if (SftpFileSystemProvider.VERSION_PARAM.equalsIgnoreCase(key)) {
-                        return;
-                    }
-
-                    Object actual = session.getObject(key);
-                    assertEquals("Mismatched value for param '" + key + "'", expected, actual);
-                });
-            }
-        }
-    }
-
-    @Test
-    public void testAttributes() throws IOException {
-        Path targetPath = detectTargetFolder();
-        Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
-            SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-        CommonTestSupportUtils.deleteRecursive(lclSftp);
-
-        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
-                GenericUtils.<String, Object>mapBuilder()
-                    .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, SftpClient.MIN_READ_BUFFER_SIZE)
-                    .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, SftpClient.MIN_WRITE_BUFFER_SIZE)
-                    .build())) {
-
-            Path parentPath = targetPath.getParent();
-            Path clientFolder = lclSftp.resolve("client");
-            String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file.txt"));
-            Path file = fs.getPath(remFilePath);
-            assertHierarchyTargetFolderExists(file.getParent());
-            Files.write(file, (getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
-
-            Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
-            assertNotNull("No attributes read for " + file, attrs);
-
-            Files.setAttribute(file, "basic:size", 2L);
-            Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
-            Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L));
-
-            FileSystem fileSystem = file.getFileSystem();
-            try {
-                UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
-                GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
-                Files.setAttribute(file, "posix:group", group);
-            } catch (UserPrincipalNotFoundException e) {
-                // Also, according to the Javadoc:
-                //      "Where an implementation does not support any notion of
-                //       group then this method always throws UserPrincipalNotFoundException."
-                // Therefore we are lenient with this exception for Windows
-                if (OsUtils.isWin32()) {
-                    System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
-                } else {
-                    throw e;
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testRootFileSystem() throws IOException {
-        Path targetPath = detectTargetFolder();
-        Path rootNative = targetPath.resolve("root").toAbsolutePath();
-        CommonTestSupportUtils.deleteRecursive(rootNative);
-        assertHierarchyTargetFolderExists(rootNative);
-
-        try (FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
-            Path dir = assertHierarchyTargetFolderExists(fs.getPath("test/foo"));
-            outputDebugMessage("Created %s", dir);
-        }
-    }
-
-    @Test   // see SSHD-697
-    public void testFileChannel() throws IOException {
-        Path targetPath = detectTargetFolder();
-        Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
-            SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
-        Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
-        Files.deleteIfExists(lclFile);
-        byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")").getBytes(StandardCharsets.UTF_8);
-        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
-            Path parentPath = targetPath.getParent();
-            String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
-            Path file = fs.getPath(remFilePath);
-
-            FileSystemProvider provider = fs.provider();
-            try (FileChannel fc = provider.newFileChannel(file, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE))) {
-                int writeLen = fc.write(ByteBuffer.wrap(expected));
-                assertEquals("Mismatched written length", expected.length, writeLen);
-
-                FileChannel fcPos = fc.position(0L);
-                assertSame("Mismatched positioned file channel", fc, fcPos);
-
-                byte[] actual = new byte[expected.length];
-                int readLen = fc.read(ByteBuffer.wrap(actual));
-                assertEquals("Mismatched read len", writeLen, readLen);
-                assertArrayEquals("Mismatched read data", expected, actual);
-            }
-        }
-
-        byte[] actual = Files.readAllBytes(lclFile);
-        assertArrayEquals("Mismatched persisted data", expected, actual);
-    }
-
-    @Test
-    public void testFileStore() throws IOException {
-        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
-            Iterable<FileStore> iter = fs.getFileStores();
-            assertTrue("Not a list", iter instanceof List<?>);
-
-            List<FileStore> list = (List<FileStore>) iter;
-            assertEquals("Mismatched stores count", 1, list.size());
-
-            FileStore store = list.get(0);
-            assertEquals("Mismatched type", SftpConstants.SFTP_SUBSYSTEM_NAME, store.type());
-            assertFalse("Read-only ?", store.isReadOnly());
-
-            for (String name : fs.supportedFileAttributeViews()) {
-                assertTrue("Unsupported view name: " + name, store.supportsFileAttributeView(name));
-            }
-
-            for (Class<? extends FileAttributeView> type : SftpFileSystemProvider.UNIVERSAL_SUPPORTED_VIEWS) {
-                assertTrue("Unsupported view type: " + type.getSimpleName(), store.supportsFileAttributeView(type));
-            }
-        }
-    }
-
-    @Test
-    public void testMultipleFileStoresOnSameProvider() throws IOException {
-        try (SshClient client = setupTestClient()) {
-            client.start();
-
-            SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
-            Collection<SftpFileSystem> fsList = new LinkedList<>();
-            try {
-                Collection<String> idSet = new HashSet<>();
-                Map<String, Object> empty = Collections.emptyMap();
-                for (int index = 0; index < 4; index++) {
-                    String credentials = getCurrentTestName() + "-user-" + index;
-                    SftpFileSystem expected = provider.newFileSystem(createFileSystemURI(credentials, empty), empty);
-                    fsList.add(expected);
-
-                    String id = expected.getId();
-                    assertTrue("Non unique file system id: " + id, idSet.add(id));
-
-                    SftpFileSystem actual = provider.getFileSystem(id);
-                    assertSame("Mismatched cached instances for " + id, expected, actual);
-                    outputDebugMessage("Created file system id: %s", id);
-                }
-
-                for (SftpFileSystem fs : fsList) {
-                    String id = fs.getId();
-                    fs.close();
-                    assertNull("File system not removed from cache: " + id, provider.getFileSystem(id));
-                }
-            } finally {
-                IOException err = null;
-                for (FileSystem fs : fsList) {
-                    try {
-                        fs.close();
-                    } catch (IOException e) {
-                        err = GenericUtils.accumulateException(err, e);
-                    }
-                }
-
-                client.stop();
-
-                if (err != null) {
-                    throw err;
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testSftpVersionSelector() throws Exception {
-        final AtomicInteger selected = new AtomicInteger(-1);
-        SftpVersionSelector selector = (session, current, available) -> {
-            int value = GenericUtils.stream(available)
-                    .mapToInt(Integer::intValue)
-                    .filter(v -> v != current)
-                    .max()
-                    .orElseGet(() -> current);
-            selected.set(value);
-            return value;
-        };
-
-        try (SshClient client = setupTestClient()) {
-            client.start();
-
-            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(5L, TimeUnit.SECONDS);
-
-                try (FileSystem fs = createSftpFileSystem(session, selector)) {
-                    assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
-                    Collection<String> views = fs.supportedFileAttributeViews();
-                    assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
-                               views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
-                    int expectedVersion = selected.get();
-                    assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
-                    testFileSystem(fs, expectedVersion);
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    private FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
-        return SftpClientFactory.instance().createSftpFileSystem(session, selector);
-    }
-
-    private void testFileSystem(FileSystem fs, int version) throws Exception {
-        Iterable<Path> rootDirs = fs.getRootDirectories();
-        for (Path root : rootDirs) {
-            String rootName = root.toString();
-            try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
-                for (Path child : ds) {
-                    String name = child.getFileName().toString();
-                    assertNotEquals("Unexpected dot name", ".", name);
-                    assertNotEquals("Unexpected dotdot name", "..", name);
-                    outputDebugMessage("[%s] %s", rootName, child);
-                }
-            } catch (IOException | RuntimeException e) {
-                // TODO on Windows one might get share problems for *.sys files
-                // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
-                // for now, Windows is less of a target so we are lenient with it
-                if (OsUtils.isWin32()) {
-                    System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
-                } else {
-                    throw e;
-                }
-            }
-        }
-
-        Path targetPath = detectTargetFolder();
-        Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
-            SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
-        CommonTestSupportUtils.deleteRecursive(lclSftp);
-
-        Path current = fs.getPath(".").toRealPath().normalize();
-        outputDebugMessage("CWD: %s", current);
-
-        Path parentPath = targetPath.getParent();
-        Path clientFolder = lclSftp.resolve("client");
-        String remFile1Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-1.txt"));
-        Path file1 = fs.getPath(remFile1Path);
-        assertHierarchyTargetFolderExists(file1.getParent());
-
-        String expected = "Hello world: " + getCurrentTestName();
-        outputDebugMessage("Write initial data to %s", file1);
-        Files.write(file1, expected.getBytes(StandardCharsets.UTF_8));
-        String buf = new String(Files.readAllBytes(file1), StandardCharsets.UTF_8);
-        assertEquals("Mismatched read test data", expected, buf);
-
-        if (version >= SftpConstants.SFTP_V4) {
-            outputDebugMessage("getFileAttributeView(%s)", file1);
-            AclFileAttributeView aclView = Files.getFileAttributeView(file1, AclFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
-            assertNotNull("No ACL view for " + file1, aclView);
-
-            Map<String, ?> attrs = Files.readAttributes(file1, "acl:*", LinkOption.NOFOLLOW_LINKS);
-            outputDebugMessage("readAttributes(%s) %s", file1, attrs);
-            assertEquals("Mismatched owner for " + file1, aclView.getOwner(), attrs.get("owner"));
-
-            @SuppressWarnings("unchecked")
-            List<AclEntry> acl = (List<AclEntry>) attrs.get("acl");
-            outputDebugMessage("acls(%s) %s", file1, acl);
-            assertListEquals("Mismatched ACLs for " + file1, aclView.getAcl(), acl);
-        }
-
-        String remFile2Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-2.txt"));
-        Path file2 = fs.getPath(remFile2Path);
-        String remFile3Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-3.txt"));
-        Path file3 = fs.getPath(remFile3Path);
-        try {
-            outputDebugMessage("Move with failure expected %s => %s", file2, file3);
-            Files.move(file2, file3, LinkOption.NOFOLLOW_LINKS);
-            fail("Unexpected success in moving " + file2 + " => " + file3);
-        } catch (NoSuchFileException e) {
-            // expected
-        }
-
-        Files.write(file2, "h".getBytes(StandardCharsets.UTF_8));
-        try {
-            outputDebugMessage("Move with failure expected %s => %s", file1, file2);
-            Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS);
-            fail("Unexpected success in moving " + file1 + " => " + file2);
-        } catch (FileAlreadyExistsException e) {
-            // expected
-        }
-
-        outputDebugMessage("Move with success expected %s => %s", file1, file2);
-        Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
-        outputDebugMessage("Move with success expected %s => %s", file2, file1);
-        Files.move(file2, file1, LinkOption.NOFOLLOW_LINKS);
-
-        Map<String, Object> attrs = Files.readAttributes(file1, "*");
-        outputDebugMessage("%s attributes: %s", file1, attrs);
-
-        // TODO there are many issues with symbolic links on Windows
-        if (OsUtils.isUNIX()) {
-            Path link = fs.getPath(remFile2Path);
-            Path linkParent = link.getParent();
-            Path relPath = linkParent.relativize(file1);
-            outputDebugMessage("Create symlink %s => %s", link, relPath);
-            Files.createSymbolicLink(link, relPath);
-            assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
-
-            Path symLink = Files.readSymbolicLink(link);
-            assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
-
-            outputDebugMessage("Delete symlink %s", link);
-            Files.delete(link);
-        }
-
-        attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
-        outputDebugMessage("%s no-follow attributes: %s", file1, attrs);
-        assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1), StandardCharsets.UTF_8));
-
-        try (FileChannel channel = FileChannel.open(file1)) {
-            try (FileLock lock = channel.lock()) {
-                outputDebugMessage("Lock %s: %s", file1, lock);
-
-                try (FileChannel channel2 = FileChannel.open(file1)) {
-                    try (FileLock lock2 = channel2.lock()) {
-                        fail("Unexpected success in re-locking " + file1 + ": " + lock2);
-                    } catch (OverlappingFileLockException e) {
-                        // expected
-                    }
-                }
-            }
-        }
-
-        Files.delete(file1);
-    }
-
-    private URI createDefaultFileSystemURI() {
-        return createDefaultFileSystemURI(Collections.emptyMap());
-    }
-
-    private URI createDefaultFileSystemURI(Map<String, ?> params) {
-        return createFileSystemURI(getCurrentTestName(), params);
-    }
-
-    private URI createFileSystemURI(String username, Map<String, ?> params) {
-        return createFileSystemURI(username, port, params);
-    }
-
-    private static URI createFileSystemURI(String username, int port, Map<String, ?> params) {
-        return SftpFileSystemProvider.createFileSystemURI(TEST_LOCALHOST, port, username, username, params);
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
deleted file mode 100644
index 1e89e47..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-import org.apache.sshd.common.SshConstants;
-import org.apache.sshd.common.auth.BasicCredentialsProvider;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.util.net.SshdSocketAddress;
-import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
-import org.apache.sshd.util.test.JUnitTestSupport;
-import org.apache.sshd.util.test.NoIoTestCase;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.MethodSorters;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-import org.junit.runners.Parameterized.UseParametersRunnerFactory;
-
-/**
- * TODO Add javadoc
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
-@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
-@Category({ NoIoTestCase.class })
-public class SftpFileSystemURITest extends JUnitTestSupport {
-    private final String host;
-    private final int port;
-    private final String username;
-    private final String password;
-    private final Map<String, ?> params;
-
-    public SftpFileSystemURITest(String host, int port, String username, String password, Map<String, ?> params) {
-        this.host = host;
-        this.port = port;
-        this.username = username;
-        this.password = password;
-        this.params = params;
-    }
-
-    @Parameters(name = "host={0}, port={1}, user={2}, password={3}, params={4}")
-    public static List<Object[]> parameters() {
-        return new ArrayList<Object[]>() {
-            // Not serializing it
-            private static final long serialVersionUID = 1L;
-
-            {
-                add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, 0, "user", "password", null});
-                add(new Object[] {"37.77.34.7", 2222, "user", "password", Collections.singletonMap("non-default-port", true)});
-                add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, SshConstants.DEFAULT_PORT, "J@ck", "d@Ripper", new HashMap<String, Object>() {
-                        // not serializing it
-                        private static final long serialVersionUID = 1L;
-
-                        {
-                            put("param1", "1st");
-                            put("param2", 2);
-                            put("param3", false);
-                        }
-                    }
-                });
-                add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null});
-            }
-        };
-    }
-
-    @Test
-    public void testFullURIEncoding() {
-        URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password, params);
-        assertEquals("Mismatched scheme", SftpConstants.SFTP_SUBSYSTEM_NAME, uri.getScheme());
-        assertEquals("Mismatched host", host, uri.getHost());
-        assertEquals("Mismatched port", port, uri.getPort());
-
-        BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(uri);
-        assertNotNull("No credentials provided", credentials);
-        assertEquals("Mismatched user", username, credentials.getUsername());
-        assertEquals("Mismatched password", password, credentials.getPassword());
-
-        Map<String, ?> uriParams = SftpFileSystemProvider.parseURIParameters(uri);
-        assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString()));
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getSimpleName()
-            + "[host=" + host
-            + ", port=" + port
-            + ", username=" + username
-            + ", password=" + password
-            + ", params=" + params
-            + "]";
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemTest.java
new file mode 100644
index 0000000..5b78325
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemTest.java
@@ -0,0 +1,532 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.UserPrincipalNotFoundException;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemEnvironment;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.apache.sshd.util.test.CoreTestSupportUtils;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@SuppressWarnings("checkstyle:MethodCount")
+public class SftpFileSystemTest extends BaseTestSupport {
+    private static SshServer sshd;
+    private static int port;
+
+    private final FileSystemFactory fileSystemFactory;
+
+    public SftpFileSystemTest() throws IOException {
+        Path targetPath = detectTargetFolder();
+        Path parentPath = targetPath.getParent();
+        fileSystemFactory = new VirtualFileSystemFactory(parentPath);
+    }
+
+    @BeforeClass
+    public static void setupServerInstance() throws Exception {
+        sshd = CoreTestSupportUtils.setupTestServer(SftpFileSystemTest.class);
+        sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+        sshd.start();
+        port = sshd.getPort();
+    }
+
+    @AfterClass
+    public static void tearDownServerInstance() throws Exception {
+        if (sshd != null) {
+            try {
+                sshd.stop(true);
+            } finally {
+                sshd = null;
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        sshd.setFileSystemFactory(fileSystemFactory);
+    }
+
+    @Test
+    public void testFileSystem() throws Exception {
+        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
+                GenericUtils.<String, Object>mapBuilder()
+                        .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
+                        .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
+                        .build())) {
+            assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+            testFileSystem(fs, ((SftpFileSystem) fs).getVersion());
+        }
+    }
+
+    @Test   // see SSHD-578
+    public void testFileSystemURIParameters() throws Exception {
+        Map<String, Object> params = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        params.put("test-class-name", getClass().getSimpleName());
+        params.put("test-pkg-name", getClass().getPackage().getName());
+        params.put("test-name", getCurrentTestName());
+
+        int expectedVersion = (SftpSubsystemEnvironment.LOWER_SFTP_IMPL + SftpSubsystemEnvironment.HIGHER_SFTP_IMPL) / 2;
+        params.put(SftpFileSystemProvider.VERSION_PARAM, expectedVersion);
+        try (SftpFileSystem fs = (SftpFileSystem) FileSystems.newFileSystem(createDefaultFileSystemURI(params), Collections.<String, Object>emptyMap())) {
+            try (SftpClient sftpClient = fs.getClient()) {
+                assertEquals("Mismatched negotiated version", expectedVersion, sftpClient.getVersion());
+
+                Session session = sftpClient.getClientSession();
+                params.forEach((key, expected) -> {
+                    if (SftpFileSystemProvider.VERSION_PARAM.equalsIgnoreCase(key)) {
+                        return;
+                    }
+
+                    Object actual = session.getObject(key);
+                    assertEquals("Mismatched value for param '" + key + "'", expected, actual);
+                });
+            }
+        }
+    }
+
+    @Test
+    public void testAttributes() throws IOException {
+        Path targetPath = detectTargetFolder();
+        Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
+            SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        CommonTestSupportUtils.deleteRecursive(lclSftp);
+
+        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
+                GenericUtils.<String, Object>mapBuilder()
+                    .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, SftpClient.MIN_READ_BUFFER_SIZE)
+                    .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, SftpClient.MIN_WRITE_BUFFER_SIZE)
+                    .build())) {
+
+            Path parentPath = targetPath.getParent();
+            Path clientFolder = lclSftp.resolve("client");
+            String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file.txt"));
+            Path file = fs.getPath(remFilePath);
+            assertHierarchyTargetFolderExists(file.getParent());
+            Files.write(file, (getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
+
+            Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
+            assertNotNull("No attributes read for " + file, attrs);
+
+            Files.setAttribute(file, "basic:size", 2L);
+            Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
+            Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L));
+
+            FileSystem fileSystem = file.getFileSystem();
+            try {
+                UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
+                GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
+                Files.setAttribute(file, "posix:group", group);
+            } catch (UserPrincipalNotFoundException e) {
+                // Also, according to the Javadoc:
+                //      "Where an implementation does not support any notion of
+                //       group then this method always throws UserPrincipalNotFoundException."
+                // Therefore we are lenient with this exception for Windows
+                if (OsUtils.isWin32()) {
+                    System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testRootFileSystem() throws IOException {
+        Path targetPath = detectTargetFolder();
+        Path rootNative = targetPath.resolve("root").toAbsolutePath();
+        CommonTestSupportUtils.deleteRecursive(rootNative);
+        assertHierarchyTargetFolderExists(rootNative);
+
+        try (FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
+            Path dir = assertHierarchyTargetFolderExists(fs.getPath("test/foo"));
+            outputDebugMessage("Created %s", dir);
+        }
+    }
+
+    @Test   // see SSHD-697
+    public void testFileChannel() throws IOException {
+        Path targetPath = detectTargetFolder();
+        Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
+            SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+        Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
+        Files.deleteIfExists(lclFile);
+        byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")").getBytes(StandardCharsets.UTF_8);
+        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
+            Path parentPath = targetPath.getParent();
+            String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
+            Path file = fs.getPath(remFilePath);
+
+            FileSystemProvider provider = fs.provider();
+            try (FileChannel fc = provider.newFileChannel(file, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE))) {
+                int writeLen = fc.write(ByteBuffer.wrap(expected));
+                assertEquals("Mismatched written length", expected.length, writeLen);
+
+                FileChannel fcPos = fc.position(0L);
+                assertSame("Mismatched positioned file channel", fc, fcPos);
+
+                byte[] actual = new byte[expected.length];
+                int readLen = fc.read(ByteBuffer.wrap(actual));
+                assertEquals("Mismatched read len", writeLen, readLen);
+                assertArrayEquals("Mismatched read data", expected, actual);
+            }
+        }
+
+        byte[] actual = Files.readAllBytes(lclFile);
+        assertArrayEquals("Mismatched persisted data", expected, actual);
+    }
+
+    @Test
+    public void testFileStore() throws IOException {
+        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
+            Iterable<FileStore> iter = fs.getFileStores();
+            assertTrue("Not a list", iter instanceof List<?>);
+
+            List<FileStore> list = (List<FileStore>) iter;
+            assertEquals("Mismatched stores count", 1, list.size());
+
+            FileStore store = list.get(0);
+            assertEquals("Mismatched type", SftpConstants.SFTP_SUBSYSTEM_NAME, store.type());
+            assertFalse("Read-only ?", store.isReadOnly());
+
+            for (String name : fs.supportedFileAttributeViews()) {
+                assertTrue("Unsupported view name: " + name, store.supportsFileAttributeView(name));
+            }
+
+            for (Class<? extends FileAttributeView> type : SftpFileSystemProvider.UNIVERSAL_SUPPORTED_VIEWS) {
+                assertTrue("Unsupported view type: " + type.getSimpleName(), store.supportsFileAttributeView(type));
+            }
+        }
+    }
+
+    @Test
+    public void testMultipleFileStoresOnSameProvider() throws IOException {
+        try (SshClient client = setupTestClient()) {
+            client.start();
+
+            SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
+            Collection<SftpFileSystem> fsList = new LinkedList<>();
+            try {
+                Collection<String> idSet = new HashSet<>();
+                Map<String, Object> empty = Collections.emptyMap();
+                for (int index = 0; index < 4; index++) {
+                    String credentials = getCurrentTestName() + "-user-" + index;
+                    SftpFileSystem expected = provider.newFileSystem(createFileSystemURI(credentials, empty), empty);
+                    fsList.add(expected);
+
+                    String id = expected.getId();
+                    assertTrue("Non unique file system id: " + id, idSet.add(id));
+
+                    SftpFileSystem actual = provider.getFileSystem(id);
+                    assertSame("Mismatched cached instances for " + id, expected, actual);
+                    outputDebugMessage("Created file system id: %s", id);
+                }
+
+                for (SftpFileSystem fs : fsList) {
+                    String id = fs.getId();
+                    fs.close();
+                    assertNull("File system not removed from cache: " + id, provider.getFileSystem(id));
+                }
+            } finally {
+                IOException err = null;
+                for (FileSystem fs : fsList) {
+                    try {
+                        fs.close();
+                    } catch (IOException e) {
+                        err = GenericUtils.accumulateException(err, e);
+                    }
+                }
+
+                client.stop();
+
+                if (err != null) {
+                    throw err;
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testSftpVersionSelector() throws Exception {
+        AtomicInteger selected = new AtomicInteger(-1);
+        SftpVersionSelector selector = (session, current, available) -> {
+            int value = GenericUtils.stream(available)
+                    .mapToInt(Integer::intValue)
+                    .filter(v -> v != current)
+                    .max()
+                    .orElseGet(() -> current);
+            selected.set(value);
+            return value;
+        };
+
+        try (SshClient client = setupTestClient()) {
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                    .verify(7L, TimeUnit.SECONDS)
+                    .getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                try (FileSystem fs = createSftpFileSystem(session, selector)) {
+                    assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+                    Collection<String> views = fs.supportedFileAttributeViews();
+                    assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
+                               views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
+                    int expectedVersion = selected.get();
+                    assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
+                    testFileSystem(fs, expectedVersion);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testFileSystemProviderServiceEntry() throws IOException {
+        Path configFile = CommonTestSupportUtils.resolve(detectSourcesFolder(),
+            MAIN_SUBFOLDER, "filtered-resources", "META-INF", "services", FileSystemProvider.class.getName());
+        assertTrue("Missing " + configFile, Files.exists(configFile));
+
+        boolean found = false;
+        try (InputStream stream = Files.newInputStream(configFile);
+             Reader r = new InputStreamReader(stream, StandardCharsets.UTF_8);
+             BufferedReader b = new BufferedReader(r)) {
+
+            for (String line = b.readLine(); line != null; line = b.readLine()) {
+                line = line.trim();
+                if (GenericUtils.isEmpty(line) || (line.charAt(0) == '#')) {
+                    continue;
+                }
+
+                assertFalse("Multiple configurations: " + line, found);
+                assertEquals("Mismatched configuration", SftpFileSystemProvider.class.getName(), line);
+                found = true;
+            }
+        }
+
+        assertTrue("No configuration found", found);
+    }
+
+    private FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
+        return SftpClientFactory.instance().createSftpFileSystem(session, selector);
+    }
+
+    private void testFileSystem(FileSystem fs, int version) throws Exception {
+        Iterable<Path> rootDirs = fs.getRootDirectories();
+        for (Path root : rootDirs) {
+            String rootName = root.toString();
+            try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+                for (Path child : ds) {
+                    String name = child.getFileName().toString();
+                    assertNotEquals("Unexpected dot name", ".", name);
+                    assertNotEquals("Unexpected dotdot name", "..", name);
+                    outputDebugMessage("[%s] %s", rootName, child);
+                }
+            } catch (IOException | RuntimeException e) {
+                // TODO on Windows one might get share problems for *.sys files
+                // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
+                // for now, Windows is less of a target so we are lenient with it
+                if (OsUtils.isWin32()) {
+                    System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        Path targetPath = detectTargetFolder();
+        Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
+            SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+        CommonTestSupportUtils.deleteRecursive(lclSftp);
+
+        Path current = fs.getPath(".").toRealPath().normalize();
+        outputDebugMessage("CWD: %s", current);
+
+        Path parentPath = targetPath.getParent();
+        Path clientFolder = lclSftp.resolve("client");
+        String remFile1Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-1.txt"));
+        Path file1 = fs.getPath(remFile1Path);
+        assertHierarchyTargetFolderExists(file1.getParent());
+
+        String expected = "Hello world: " + getCurrentTestName();
+        outputDebugMessage("Write initial data to %s", file1);
+        Files.write(file1, expected.getBytes(StandardCharsets.UTF_8));
+        String buf = new String(Files.readAllBytes(file1), StandardCharsets.UTF_8);
+        assertEquals("Mismatched read test data", expected, buf);
+
+        if (version >= SftpConstants.SFTP_V4) {
+            outputDebugMessage("getFileAttributeView(%s)", file1);
+            AclFileAttributeView aclView = Files.getFileAttributeView(file1, AclFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
+            assertNotNull("No ACL view for " + file1, aclView);
+
+            Map<String, ?> attrs = Files.readAttributes(file1, "acl:*", LinkOption.NOFOLLOW_LINKS);
+            outputDebugMessage("readAttributes(%s) %s", file1, attrs);
+            assertEquals("Mismatched owner for " + file1, aclView.getOwner(), attrs.get("owner"));
+
+            @SuppressWarnings("unchecked")
+            List<AclEntry> acl = (List<AclEntry>) attrs.get("acl");
+            outputDebugMessage("acls(%s) %s", file1, acl);
+            assertListEquals("Mismatched ACLs for " + file1, aclView.getAcl(), acl);
+        }
+
+        String remFile2Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-2.txt"));
+        Path file2 = fs.getPath(remFile2Path);
+        String remFile3Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-3.txt"));
+        Path file3 = fs.getPath(remFile3Path);
+        try {
+            outputDebugMessage("Move with failure expected %s => %s", file2, file3);
+            Files.move(file2, file3, LinkOption.NOFOLLOW_LINKS);
+            fail("Unexpected success in moving " + file2 + " => " + file3);
+        } catch (NoSuchFileException e) {
+            // expected
+        }
+
+        Files.write(file2, "h".getBytes(StandardCharsets.UTF_8));
+        try {
+            outputDebugMessage("Move with failure expected %s => %s", file1, file2);
+            Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS);
+            fail("Unexpected success in moving " + file1 + " => " + file2);
+        } catch (FileAlreadyExistsException e) {
+            // expected
+        }
+
+        outputDebugMessage("Move with success expected %s => %s", file1, file2);
+        Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
+        outputDebugMessage("Move with success expected %s => %s", file2, file1);
+        Files.move(file2, file1, LinkOption.NOFOLLOW_LINKS);
+
+        Map<String, Object> attrs = Files.readAttributes(file1, "*");
+        outputDebugMessage("%s attributes: %s", file1, attrs);
+
+        // TODO there are many issues with symbolic links on Windows
+        if (OsUtils.isUNIX()) {
+            Path link = fs.getPath(remFile2Path);
+            Path linkParent = link.getParent();
+            Path relPath = linkParent.relativize(file1);
+            outputDebugMessage("Create symlink %s => %s", link, relPath);
+            Files.createSymbolicLink(link, relPath);
+            assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
+
+            Path symLink = Files.readSymbolicLink(link);
+            assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
+
+            outputDebugMessage("Delete symlink %s", link);
+            Files.delete(link);
+        }
+
+        attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
+        outputDebugMessage("%s no-follow attributes: %s", file1, attrs);
+        assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1), StandardCharsets.UTF_8));
+
+        try (FileChannel channel = FileChannel.open(file1)) {
+            try (FileLock lock = channel.lock()) {
+                outputDebugMessage("Lock %s: %s", file1, lock);
+
+                try (FileChannel channel2 = FileChannel.open(file1)) {
+                    try (FileLock lock2 = channel2.lock()) {
+                        fail("Unexpected success in re-locking " + file1 + ": " + lock2);
+                    } catch (OverlappingFileLockException e) {
+                        // expected
+                    }
+                }
+            }
+        }
+
+        Files.delete(file1);
+    }
+
+    private URI createDefaultFileSystemURI() {
+        return createDefaultFileSystemURI(Collections.emptyMap());
+    }
+
+    private URI createDefaultFileSystemURI(Map<String, ?> params) {
+        return createFileSystemURI(getCurrentTestName(), params);
+    }
+
+    private URI createFileSystemURI(String username, Map<String, ?> params) {
+        return createFileSystemURI(username, port, params);
+    }
+
+    private static URI createFileSystemURI(String username, int port, Map<String, ?> params) {
+        return SftpFileSystemProvider.createFileSystemURI(TEST_LOCALHOST, port, username, username, params);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
new file mode 100644
index 0000000..a8bfaf8
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@Category({ NoIoTestCase.class })
+public class SftpFileSystemURITest extends JUnitTestSupport {
+    private final String host;
+    private final int port;
+    private final String username;
+    private final String password;
+    private final Map<String, ?> params;
+
+    public SftpFileSystemURITest(String host, int port, String username, String password, Map<String, ?> params) {
+        this.host = host;
+        this.port = port;
+        this.username = username;
+        this.password = password;
+        this.params = params;
+    }
+
+    @Parameters(name = "host={0}, port={1}, user={2}, password={3}, params={4}")
+    public static List<Object[]> parameters() {
+        return new ArrayList<Object[]>() {
+            // Not serializing it
+            private static final long serialVersionUID = 1L;
+
+            {
+                add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, 0, "user", "password", null});
+                add(new Object[] {"37.77.34.7", 2222, "user", "password", Collections.singletonMap("non-default-port", true)});
+                add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, SshConstants.DEFAULT_PORT, "J@ck", "d@Ripper", new HashMap<String, Object>() {
+                        // not serializing it
+                        private static final long serialVersionUID = 1L;
+
+                        {
+                            put("param1", "1st");
+                            put("param2", 2);
+                            put("param3", false);
+                        }
+                    }
+                });
+                add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null});
+            }
+        };
+    }
+
+    @Test
+    public void testFullURIEncoding() {
+        URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password, params);
+        assertEquals("Mismatched scheme", SftpConstants.SFTP_SUBSYSTEM_NAME, uri.getScheme());
+        assertEquals("Mismatched host", host, uri.getHost());
+        assertEquals("Mismatched port", port, uri.getPort());
+
+        BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(uri);
+        assertNotNull("No credentials provided", credentials);
+        assertEquals("Mismatched user", username, credentials.getUsername());
+        assertEquals("Mismatched password", password, credentials.getPassword());
+
+        Map<String, ?> uriParams = SftpFileSystemProvider.parseURIParameters(uri);
+        assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString()));
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName()
+            + "[host=" + host
+            + ", port=" + port
+            + ", username=" + username
+            + ", password=" + password
+            + ", params=" + params
+            + "]";
+    }
+}


[6/8] mina-sshd git commit: [SSHD-861] Added SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
deleted file mode 100644
index f8cd75b..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ /dev/null
@@ -1,1312 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.channels.FileChannel;
-import java.nio.charset.Charset;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.AccessMode;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.FileSystemException;
-import java.nio.file.FileSystemNotFoundException;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.ProviderMismatchException;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileOwnerAttributeView;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
-import org.apache.sshd.common.PropertyResolver;
-import org.apache.sshd.common.PropertyResolverUtils;
-import org.apache.sshd.common.SshConstants;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.auth.BasicCredentialsImpl;
-import org.apache.sshd.common.auth.BasicCredentialsProvider;
-import org.apache.sshd.common.auth.MutableBasicCredentials;
-import org.apache.sshd.common.io.IoSession;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.subsystem.sftp.SftpException;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.NumberUtils;
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A registered {@link FileSystemProvider} that registers the &quot;sftp://&quot;
- * scheme so that URLs with this protocol are handled as remote SFTP {@link Path}-s
- * - e.g., &quot;{@code sftp://user:password@host/remote/file/path}&quot;
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpFileSystemProvider extends FileSystemProvider {
-    public static final String READ_BUFFER_PROP_NAME = "sftp-fs-read-buffer-size";
-    public static final int DEFAULT_READ_BUFFER_SIZE = SftpClient.DEFAULT_READ_BUFFER_SIZE;
-    public static final String WRITE_BUFFER_PROP_NAME = "sftp-fs-write-buffer-size";
-    public static final int DEFAULT_WRITE_BUFFER_SIZE = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
-    public static final String CONNECT_TIME_PROP_NAME = "sftp-fs-connect-time";
-    public static final long DEFAULT_CONNECT_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
-    public static final String AUTH_TIME_PROP_NAME = "sftp-fs-auth-time";
-    public static final long DEFAULT_AUTH_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
-    public static final String NAME_DECORDER_CHARSET_PROP_NAME = "sftp-fs-name-decoder-charset";
-    public static final Charset DEFAULT_NAME_DECODER_CHARSET = SftpClient.DEFAULT_NAME_DECODING_CHARSET;
-
-    /**
-     * <P>
-     * URI parameter that can be used to specify a special version selection. Options are:
-     * </P>
-     * <UL>
-     *      <LI>{@code max} - select maximum available version for the client</LI>
-     *      <LI>{@code min} - select minimum available version for the client</LI>
-     *      <LI>{@code current} - whatever version is reported by the server</LI>
-     *      <LI>{@code nnn} - select <U>only</U> the specified version</LI>
-     *      <LI>{@code a,b,c} - select one of the specified versions (if available) in preference order</LI>
-     * </UL>
-     */
-    public static final String VERSION_PARAM = "version";
-
-    public static final Set<Class<? extends FileAttributeView>> UNIVERSAL_SUPPORTED_VIEWS =
-        Collections.unmodifiableSet(
-            GenericUtils.asSet(
-                PosixFileAttributeView.class,
-                FileOwnerAttributeView.class,
-                BasicFileAttributeView.class
-            ));
-
-    protected final Logger log;
-
-    private final SshClient client;
-    private final SftpClientFactory factory;
-    private final SftpVersionSelector selector;
-    private final NavigableMap<String, SftpFileSystem> fileSystems = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
-    public SftpFileSystemProvider() {
-        this((SshClient) null);
-    }
-
-    public SftpFileSystemProvider(SftpVersionSelector selector) {
-        this(null, selector);
-    }
-
-    /**
-     * @param client The {@link SshClient} to use - if {@code null} then a
-     *               default one will be setup and started. Otherwise, it is assumed that
-     *               the client has already been started
-     * @see SshClient#setUpDefaultClient()
-     */
-    public SftpFileSystemProvider(SshClient client) {
-        this(client, SftpVersionSelector.CURRENT);
-    }
-
-    public SftpFileSystemProvider(SshClient client, SftpVersionSelector selector) {
-        this(client, null, selector);
-    }
-
-    public SftpFileSystemProvider(SshClient client, SftpClientFactory factory, SftpVersionSelector selector) {
-        this.log = LoggerFactory.getLogger(getClass());
-        this.factory = factory;
-        this.selector = selector;
-        if (client == null) {
-            // TODO: make this configurable using system properties
-            client = SshClient.setUpDefaultClient();
-            client.start();
-        }
-        this.client = client;
-    }
-
-    @Override
-    public String getScheme() {
-        return SftpConstants.SFTP_SUBSYSTEM_NAME;
-    }
-
-    public final SftpVersionSelector getSftpVersionSelector() {
-        return selector;
-    }
-
-    @Override // NOTE: co-variant return
-    public SftpFileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
-        String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided");
-        int port = uri.getPort();
-        if (port <= 0) {
-            port = SshConstants.DEFAULT_PORT;
-        }
-
-        BasicCredentialsProvider credentials = parseCredentials(uri);
-        ValidateUtils.checkState(credentials != null, "No credentials provided");
-        String username = credentials.getUsername();
-        String id = getFileSystemIdentifier(host, port, username);
-        Map<String, Object> params = resolveFileSystemParameters(env, parseURIParameters(uri));
-        PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(params);
-        SftpVersionSelector selector = resolveSftpVersionSelector(uri, getSftpVersionSelector(), resolver);
-        Charset decodingCharset =
-            PropertyResolverUtils.getCharset(resolver, NAME_DECORDER_CHARSET_PROP_NAME, DEFAULT_NAME_DECODER_CHARSET);
-        long maxConnectTime = resolver.getLongProperty(CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME);
-        long maxAuthTime = resolver.getLongProperty(AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME);
-        String password = credentials.getPassword();
-
-        SftpFileSystem fileSystem;
-        synchronized (fileSystems) {
-            if (fileSystems.containsKey(id)) {
-                throw new FileSystemAlreadyExistsException(id);
-            }
-
-            // TODO try and find a way to avoid doing this while locking the file systems cache
-            ClientSession session = null;
-            try {
-                session = client.connect(username, host, port)
-                    .verify(maxConnectTime)
-                    .getSession();
-                if (GenericUtils.size(params) > 0) {
-                    // Cannot use forEach because the session is not effectively final
-                    for (Map.Entry<String, ?> pe : params.entrySet()) {
-                        String key = pe.getKey();
-                        Object value = pe.getValue();
-                        if (VERSION_PARAM.equalsIgnoreCase(key)) {
-                            continue;
-                        }
-
-                        PropertyResolverUtils.updateProperty(session, key, value);
-                    }
-
-                    PropertyResolverUtils.updateProperty(session, SftpClient.NAME_DECODING_CHARSET, decodingCharset);
-                }
-
-                session.addPasswordIdentity(password);
-                session.auth().verify(maxAuthTime);
-
-                fileSystem = new SftpFileSystem(this, id, session, factory, selector);
-                fileSystems.put(id, fileSystem);
-            } catch (Exception e) {
-                if (session != null) {
-                    try {
-                        session.close();
-                    } catch (IOException t) {
-                        if (log.isDebugEnabled()) {
-                            log.debug("Failed (" + t.getClass().getSimpleName() + ")"
-                                    + " to close session for new file system on " + host + ":" + port
-                                    + " due to " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]"
-                                    + ": " + t.getMessage());
-                        }
-                    }
-                }
-
-                if (e instanceof IOException) {
-                    throw (IOException) e;
-                } else if (e instanceof RuntimeException) {
-                    throw (RuntimeException) e;
-                } else {
-                    throw new IOException(e);
-                }
-            }
-        }
-
-        fileSystem.setReadBufferSize(resolver.getIntProperty(READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
-        fileSystem.setWriteBufferSize(resolver.getIntProperty(WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
-        if (log.isDebugEnabled()) {
-            log.debug("newFileSystem({}): {}", uri.toASCIIString(), fileSystem);
-        }
-        return fileSystem;
-    }
-
-    protected SftpVersionSelector resolveSftpVersionSelector(URI uri, SftpVersionSelector defaultSelector, PropertyResolver resolver) {
-        String preference = resolver.getString(VERSION_PARAM);
-        if (GenericUtils.isEmpty(preference)) {
-            return defaultSelector;
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("resolveSftpVersionSelector({}) preference={}", uri, preference);
-        }
-
-        if ("max".equalsIgnoreCase(preference)) {
-            return SftpVersionSelector.MAXIMUM;
-        } else if ("min".equalsIgnoreCase(preference)) {
-            return SftpVersionSelector.MINIMUM;
-        } else if ("current".equalsIgnoreCase(preference)) {
-            return SftpVersionSelector.CURRENT;
-        }
-
-        String[] values = GenericUtils.split(preference, ',');
-        if (values.length == 1) {
-            return SftpVersionSelector.fixedVersionSelector(Integer.parseInt(values[0]));
-        }
-
-        int[] preferred = new int[values.length];
-        for (int index = 0; index < values.length; index++) {
-            preferred[index] = Integer.parseInt(values[index]);
-        }
-
-        return SftpVersionSelector.preferredVersionSelector(preferred);
-    }
-
-    // NOTE: URI parameters override environment ones
-    public static Map<String, Object> resolveFileSystemParameters(Map<String, ?> env, Map<String, Object> uriParams) {
-        if (GenericUtils.isEmpty(env)) {
-            return GenericUtils.isEmpty(uriParams) ? Collections.emptyMap() : uriParams;
-        } else if (GenericUtils.isEmpty(uriParams)) {
-            return Collections.unmodifiableMap(env);
-        }
-
-        Map<String, Object> resolved = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        resolved.putAll(env);
-        resolved.putAll(uriParams);
-        return resolved;
-    }
-
-    /**
-     * Attempts to parse the user information from the URI
-     *
-     * @param uri The {@link URI} value - ignored if {@code null} or does not
-     * contain any {@link URI#getUserInfo() user info}.
-     * @return The parsed credentials - {@code null} if none available
-     */
-    public static MutableBasicCredentials parseCredentials(URI uri) {
-        return parseCredentials((uri == null) ? "" : uri.getUserInfo());
-    }
-
-    public static MutableBasicCredentials parseCredentials(String userInfo) {
-        if (GenericUtils.isEmpty(userInfo)) {
-            return null;
-        }
-
-        String[] ui = GenericUtils.split(userInfo, ':');
-        ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
-        return new BasicCredentialsImpl(ui[0], ui[1]);
-    }
-
-    public static Map<String, Object> parseURIParameters(URI uri) {
-        return parseURIParameters((uri == null) ? "" : uri.getQuery());
-    }
-
-    public static Map<String, Object> parseURIParameters(String params) {
-        if (GenericUtils.isEmpty(params)) {
-            return Collections.emptyMap();
-        }
-
-        if (params.charAt(0) == '?') {
-            if (params.length() == 1) {
-                return Collections.emptyMap();
-            }
-            params = params.substring(1);
-        }
-
-        String[] pairs = GenericUtils.split(params, '&');
-        Map<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        for (String p : pairs) {
-            int pos = p.indexOf('=');
-            if (pos < 0) {
-                map.put(p, Boolean.TRUE);
-                continue;
-            }
-
-            String key = p.substring(0, pos);
-            String value = p.substring(pos + 1);
-            if (NumberUtils.isIntegerNumber(value)) {
-                map.put(key, Long.valueOf(value));
-            } else if ("true".equals(value) || "false".equals("value")) {
-                map.put(key, Boolean.valueOf(value));
-            } else {
-                map.put(key, value);
-            }
-        }
-
-        return map;
-    }
-
-    public SftpFileSystem newFileSystem(ClientSession session) throws IOException {
-        String id = getFileSystemIdentifier(session);
-        SftpFileSystem fileSystem;
-        synchronized (fileSystems) {
-            if (fileSystems.containsKey(id)) {
-                throw new FileSystemAlreadyExistsException(id);
-            }
-            fileSystem = new SftpFileSystem(this, id, session, factory, getSftpVersionSelector());
-            fileSystems.put(id, fileSystem);
-        }
-
-        fileSystem.setReadBufferSize(session.getIntProperty(READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
-        fileSystem.setWriteBufferSize(session.getIntProperty(WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
-        if (log.isDebugEnabled()) {
-            log.debug("newFileSystem: {}", fileSystem);
-        }
-
-        return fileSystem;
-    }
-
-    @Override
-    public FileSystem getFileSystem(URI uri) {
-        String id = getFileSystemIdentifier(uri);
-        SftpFileSystem fs = getFileSystem(id);
-        if (fs == null) {
-            throw new FileSystemNotFoundException(id);
-        }
-        return fs;
-    }
-
-    /**
-     * @param id File system identifier - ignored if {@code null}/empty
-     * @return The removed {@link SftpFileSystem} - {@code null} if no match
-     */
-    public SftpFileSystem removeFileSystem(String id) {
-        if (GenericUtils.isEmpty(id)) {
-            return null;
-        }
-
-        SftpFileSystem removed;
-        synchronized (fileSystems) {
-            removed = fileSystems.remove(id);
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("removeFileSystem({}): {}", id, removed);
-        }
-        return removed;
-    }
-
-    /**
-     * @param id File system identifier - ignored if {@code null}/empty
-     * @return The cached {@link SftpFileSystem} - {@code null} if no match
-     */
-    public SftpFileSystem getFileSystem(String id) {
-        if (GenericUtils.isEmpty(id)) {
-            return null;
-        }
-
-        synchronized (fileSystems) {
-            return fileSystems.get(id);
-        }
-    }
-
-    @Override
-    public Path getPath(URI uri) {
-        FileSystem fs = getFileSystem(uri);
-        return fs.getPath(uri.getPath());
-    }
-
-    @Override
-    public FileChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
-        return newFileChannel(path, options, attrs);
-    }
-
-    @Override
-    public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
-        Collection<SftpClient.OpenMode> modes = SftpClient.OpenMode.fromOpenOptions(options);
-        if (modes.isEmpty()) {
-            modes = EnumSet.of(SftpClient.OpenMode.Read, SftpClient.OpenMode.Write);
-        }
-        // TODO: process file attributes
-        return new SftpFileSystemChannel(toSftpPath(path), modes);
-    }
-
-    @Override
-    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
-        final SftpPath p = toSftpPath(dir);
-        return new SftpDirectoryStream(p);
-    }
-
-    @Override
-    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
-        SftpPath p = toSftpPath(dir);
-        SftpFileSystem fs = p.getFileSystem();
-        if (log.isDebugEnabled()) {
-            log.debug("createDirectory({}) {} ({})", fs, dir, Arrays.asList(attrs));
-        }
-        try (SftpClient sftp = fs.getClient()) {
-            try {
-                sftp.mkdir(dir.toString());
-            } catch (SftpException e) {
-                int sftpStatus = e.getStatus();
-                if ((sftp.getVersion() == SftpConstants.SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
-                    try {
-                        Attributes attributes = sftp.stat(dir.toString());
-                        if (attributes != null) {
-                            throw new FileAlreadyExistsException(p.toString());
-                        }
-                    } catch (SshException e2) {
-                        e.addSuppressed(e2);
-                    }
-                }
-                if (sftpStatus == SftpConstants.SSH_FX_FILE_ALREADY_EXISTS) {
-                    throw new FileAlreadyExistsException(p.toString());
-                }
-                throw e;
-            }
-            for (FileAttribute<?> attr : attrs) {
-                setAttribute(p, attr.name(), attr.value());
-            }
-        }
-    }
-
-    @Override
-    public void delete(Path path) throws IOException {
-        SftpPath p = toSftpPath(path);
-        checkAccess(p, AccessMode.WRITE);
-
-        SftpFileSystem fs = p.getFileSystem();
-        if (log.isDebugEnabled()) {
-            log.debug("delete({}) {}", fs, path);
-        }
-
-        try (SftpClient sftp = fs.getClient()) {
-            BasicFileAttributes attributes = readAttributes(path, BasicFileAttributes.class);
-            if (attributes.isDirectory()) {
-                sftp.rmdir(path.toString());
-            } else {
-                sftp.remove(path.toString());
-            }
-        }
-    }
-
-    @Override
-    public void copy(Path source, Path target, CopyOption... options) throws IOException {
-        SftpPath src = toSftpPath(source);
-        SftpPath dst = toSftpPath(target);
-        if (src.getFileSystem() != dst.getFileSystem()) {
-            throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
-        }
-        checkAccess(src);
-
-        boolean replaceExisting = false;
-        boolean copyAttributes = false;
-        boolean noFollowLinks = false;
-        for (CopyOption opt : options) {
-            replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
-            copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
-            noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
-        }
-        LinkOption[] linkOptions = IoUtils.getLinkOptions(!noFollowLinks);
-
-        // attributes of source file
-        BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
-        if (attrs.isSymbolicLink()) {
-            throw new IOException("Copying of symbolic links not supported");
-        }
-
-        // delete target if it exists and REPLACE_EXISTING is specified
-        Boolean status = IoUtils.checkFileExists(target, linkOptions);
-        if (status == null) {
-            throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("copy({})[{}] {} => {}", src.getFileSystem(), Arrays.asList(options), src, dst);
-        }
-
-        if (replaceExisting) {
-            deleteIfExists(target);
-        } else {
-            if (status) {
-                throw new FileAlreadyExistsException(target.toString());
-            }
-        }
-
-        // create directory or copy file
-        if (attrs.isDirectory()) {
-            createDirectory(target);
-        } else {
-            try (InputStream in = newInputStream(source);
-                 OutputStream os = newOutputStream(target)) {
-                IoUtils.copy(in, os);
-            }
-        }
-
-        // copy basic attributes to target
-        if (copyAttributes) {
-            BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
-            try {
-                view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
-            } catch (Throwable x) {
-                // rollback
-                try {
-                    delete(target);
-                } catch (Throwable suppressed) {
-                    x.addSuppressed(suppressed);
-                }
-                throw x;
-            }
-        }
-    }
-
-    @Override
-    public void move(Path source, Path target, CopyOption... options) throws IOException {
-        SftpPath src = toSftpPath(source);
-        SftpFileSystem fsSrc = src.getFileSystem();
-        SftpPath dst = toSftpPath(target);
-
-        if (src.getFileSystem() != dst.getFileSystem()) {
-            throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
-        }
-        checkAccess(src);
-
-        boolean replaceExisting = false;
-        boolean copyAttributes = false;
-        boolean noFollowLinks = false;
-        for (CopyOption opt : options) {
-            replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
-            copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
-            noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
-        }
-        LinkOption[] linkOptions = IoUtils.getLinkOptions(noFollowLinks);
-
-        // attributes of source file
-        BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
-        if (attrs.isSymbolicLink()) {
-            throw new IOException("Moving of source symbolic link (" + source + ") to " + target + " not supported");
-        }
-
-        // delete target if it exists and REPLACE_EXISTING is specified
-        Boolean status = IoUtils.checkFileExists(target, linkOptions);
-        if (status == null) {
-            throw new AccessDeniedException("Existence cannot be determined for move target " + target);
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("move({})[{}] {} => {}", src.getFileSystem(), Arrays.asList(options), src, dst);
-        }
-
-        if (replaceExisting) {
-            deleteIfExists(target);
-        } else if (status) {
-            throw new FileAlreadyExistsException(target.toString());
-        }
-
-        try (SftpClient sftp = fsSrc.getClient()) {
-            sftp.rename(src.toString(), dst.toString());
-        }
-
-        // copy basic attributes to target
-        if (copyAttributes) {
-            BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
-            try {
-                view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
-            } catch (Throwable x) {
-                // rollback
-                try {
-                    delete(target);
-                } catch (Throwable suppressed) {
-                    x.addSuppressed(suppressed);
-                }
-                throw x;
-            }
-        }
-    }
-
-    @Override
-    public boolean isSameFile(Path path1, Path path2) throws IOException {
-        SftpPath p1 = toSftpPath(path1);
-        SftpPath p2 = toSftpPath(path2);
-        if (p1.getFileSystem() != p2.getFileSystem()) {
-            throw new ProviderMismatchException("Mismatched file system providers for " + p1 + " vs. " + p2);
-        }
-        checkAccess(p1);
-        checkAccess(p2);
-        return p1.equals(p2);
-    }
-
-    @Override
-    public boolean isHidden(Path path) throws IOException {
-        return false;
-    }
-
-    @Override
-    public FileStore getFileStore(Path path) throws IOException {
-        FileSystem fs = path.getFileSystem();
-        if (!(fs instanceof SftpFileSystem)) {
-            throw new FileSystemException(path.toString(), path.toString(), "getFileStore(" + path + ") path not attached to an SFTP file system");
-        }
-
-        SftpFileSystem sftpFs = (SftpFileSystem) fs;
-        String id = sftpFs.getId();
-        SftpFileSystem cached = getFileSystem(id);
-        if (cached != sftpFs) {
-            throw new FileSystemException(path.toString(), path.toString(), "Mismatched file system instance for id=" + id);
-        }
-
-        return sftpFs.getFileStores().get(0);
-    }
-
-    @Override
-    public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
-        SftpPath l = toSftpPath(link);
-        SftpFileSystem fsLink = l.getFileSystem();
-        SftpPath t = toSftpPath(target);
-        if (fsLink != t.getFileSystem()) {
-            throw new ProviderMismatchException("Mismatched file system providers for " + l + " vs. " + t);
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("createSymbolicLink({})[{}] {} => {}", fsLink, Arrays.asList(attrs), link, target);
-        }
-
-        try (SftpClient client = fsLink.getClient()) {
-            client.symLink(l.toString(), t.toString());
-        }
-    }
-
-    @Override
-    public Path readSymbolicLink(Path link) throws IOException {
-        SftpPath l = toSftpPath(link);
-        SftpFileSystem fsLink = l.getFileSystem();
-        try (SftpClient client = fsLink.getClient()) {
-            String linkPath = client.readLink(l.toString());
-            if (log.isDebugEnabled()) {
-                log.debug("readSymbolicLink({})[{}] {} => {}", fsLink, link, linkPath);
-            }
-
-            return fsLink.getPath(linkPath);
-        }
-    }
-
-    @Override
-    public void checkAccess(Path path, AccessMode... modes) throws IOException {
-        SftpPath p = toSftpPath(path);
-        boolean w = false;
-        boolean x = false;
-        if (GenericUtils.length(modes) > 0) {
-            for (AccessMode mode : modes) {
-                switch (mode) {
-                    case READ:
-                        break;
-                    case WRITE:
-                        w = true;
-                        break;
-                    case EXECUTE:
-                        x = true;
-                        break;
-                    default:
-                        throw new UnsupportedOperationException("Unsupported mode: " + mode);
-                }
-            }
-        }
-
-        BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
-        if ((attrs == null) && !(p.isAbsolute() && p.getNameCount() == 0)) {
-            throw new NoSuchFileException(path.toString());
-        }
-
-        SftpFileSystem fs = p.getFileSystem();
-        if (x || (w && fs.isReadOnly())) {
-            throw new AccessDeniedException("Filesystem is read-only: " + path.toString());
-        }
-    }
-
-    @Override
-    public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, final LinkOption... options) {
-        if (isSupportedFileAttributeView(path, type)) {
-            if (AclFileAttributeView.class.isAssignableFrom(type)) {
-                return type.cast(new SftpAclFileAttributeView(this, path, options));
-            } else if (BasicFileAttributeView.class.isAssignableFrom(type)) {
-                return type.cast(new SftpPosixFileAttributeView(this, path, options));
-            }
-        }
-
-        throw new UnsupportedOperationException("getFileAttributeView(" + path + ") view not supported: " + type.getSimpleName());
-    }
-
-    public boolean isSupportedFileAttributeView(Path path, Class<? extends FileAttributeView> type) {
-        return isSupportedFileAttributeView(toSftpPath(path).getFileSystem(), type);
-    }
-
-    public boolean isSupportedFileAttributeView(SftpFileSystem fs, Class<? extends FileAttributeView> type) {
-        Collection<String> views = fs.supportedFileAttributeViews();
-        if ((type == null) || GenericUtils.isEmpty(views)) {
-            return false;
-        } else if (PosixFileAttributeView.class.isAssignableFrom(type)) {
-            return views.contains("posix");
-        } else if (AclFileAttributeView.class.isAssignableFrom(type)) {
-            return views.contains("acl");   // must come before owner view
-        } else if (FileOwnerAttributeView.class.isAssignableFrom(type)) {
-            return views.contains("owner");
-        } else if (BasicFileAttributeView.class.isAssignableFrom(type)) {
-            return views.contains("basic"); // must be last
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
-        if (type.isAssignableFrom(PosixFileAttributes.class)) {
-            return type.cast(getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes());
-        }
-
-        throw new UnsupportedOperationException("readAttributes(" + path + ")[" + type.getSimpleName() + "] N/A");
-    }
-
-    @Override
-    public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
-        String view;
-        String attrs;
-        int i = attributes.indexOf(':');
-        if (i == -1) {
-            view = "basic";
-            attrs = attributes;
-        } else {
-            view = attributes.substring(0, i++);
-            attrs = attributes.substring(i);
-        }
-
-        return readAttributes(path, view, attrs, options);
-    }
-
-    public Map<String, Object> readAttributes(Path path, String view, String attrs, LinkOption... options) throws IOException {
-        SftpPath p = toSftpPath(path);
-        SftpFileSystem fs = p.getFileSystem();
-        Collection<String> views = fs.supportedFileAttributeViews();
-        if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
-            throw new UnsupportedOperationException("readAttributes(" + path + ")[" + view + ":" + attrs + "] view not supported: " + views);
-        }
-
-        if ("basic".equalsIgnoreCase(view) || "posix".equalsIgnoreCase(view) || "owner".equalsIgnoreCase(view)) {
-            return readPosixViewAttributes(p, view, attrs, options);
-        } else if ("acl".equalsIgnoreCase(view)) {
-            return readAclViewAttributes(p, view, attrs, options);
-        } else  {
-            return readCustomViewAttributes(p, view, attrs, options);
-        }
-    }
-
-    protected Map<String, Object> readCustomViewAttributes(SftpPath path, String view, String attrs, LinkOption... options) throws IOException {
-        throw new UnsupportedOperationException("readCustomViewAttributes(" + path + ")[" + view + ":" + attrs + "] view not supported");
-    }
-
-    protected NavigableMap<String, Object> readAclViewAttributes(SftpPath path, String view, String attrs, LinkOption... options) throws IOException {
-        if ("*".equals(attrs)) {
-            attrs = "acl,owner";
-        }
-
-        SftpFileSystem fs = path.getFileSystem();
-        SftpClient.Attributes attributes;
-        try (SftpClient client = fs.getClient()) {
-            attributes = readRemoteAttributes(path, options);
-        }
-
-        NavigableMap<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        String[] attrValues = GenericUtils.split(attrs, ',');
-        boolean traceEnabled = log.isTraceEnabled();
-        for (String attr : attrValues) {
-            switch (attr) {
-                case "acl":
-                    List<AclEntry> acl = attributes.getAcl();
-                    if (acl != null) {
-                        map.put(attr, acl);
-                    }
-                    break;
-                case "owner":
-                    String owner = attributes.getOwner();
-                    if (GenericUtils.length(owner) > 0) {
-                        map.put(attr, new SftpFileSystem.DefaultUserPrincipal(owner));
-                    }
-                    break;
-                default:
-                    if (traceEnabled) {
-                        log.trace("readAclViewAttributes({})[{}] unknown attribute: {}", fs, attrs, attr);
-                    }
-            }
-        }
-
-        return map;
-    }
-
-    public SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... options) throws IOException {
-        SftpFileSystem fs = path.getFileSystem();
-        try (SftpClient client = fs.getClient()) {
-            try {
-                SftpClient.Attributes attrs;
-                if (IoUtils.followLinks(options)) {
-                    attrs = client.stat(path.toString());
-                } else {
-                    attrs = client.lstat(path.toString());
-                }
-                if (log.isTraceEnabled()) {
-                    log.trace("readRemoteAttributes({})[{}]: {}", fs, path, attrs);
-                }
-                return attrs;
-            } catch (SftpException e) {
-                if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
-                    throw new NoSuchFileException(path.toString());
-                }
-                throw e;
-            }
-        }
-    }
-
-    protected NavigableMap<String, Object> readPosixViewAttributes(
-            SftpPath path, String view, String attrs, LinkOption... options)
-                throws IOException {
-        PosixFileAttributes v = readAttributes(path, PosixFileAttributes.class, options);
-        if ("*".equals(attrs)) {
-            attrs = "lastModifiedTime,lastAccessTime,creationTime,size,isRegularFile,isDirectory,isSymbolicLink,isOther,fileKey,owner,permissions,group";
-        }
-
-        NavigableMap<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        boolean traceEnabled = log.isTraceEnabled();
-        String[] attrValues = GenericUtils.split(attrs, ',');
-        for (String attr : attrValues) {
-            switch (attr) {
-                case "lastModifiedTime":
-                    map.put(attr, v.lastModifiedTime());
-                    break;
-                case "lastAccessTime":
-                    map.put(attr, v.lastAccessTime());
-                    break;
-                case "creationTime":
-                    map.put(attr, v.creationTime());
-                    break;
-                case "size":
-                    map.put(attr, v.size());
-                    break;
-                case "isRegularFile":
-                    map.put(attr, v.isRegularFile());
-                    break;
-                case "isDirectory":
-                    map.put(attr, v.isDirectory());
-                    break;
-                case "isSymbolicLink":
-                    map.put(attr, v.isSymbolicLink());
-                    break;
-                case "isOther":
-                    map.put(attr, v.isOther());
-                    break;
-                case "fileKey":
-                    map.put(attr, v.fileKey());
-                    break;
-                case "owner":
-                    map.put(attr, v.owner());
-                    break;
-                case "permissions":
-                    map.put(attr, v.permissions());
-                    break;
-                case "group":
-                    map.put(attr, v.group());
-                    break;
-                default:
-                    if (traceEnabled) {
-                        log.trace("readPosixViewAttributes({})[{}:{}] ignored for {}", path, view, attr, attrs);
-                    }
-            }
-        }
-        return map;
-    }
-
-    @Override
-    public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
-        String view;
-        String attr;
-        int i = attribute.indexOf(':');
-        if (i == -1) {
-            view = "basic";
-            attr = attribute;
-        } else {
-            view = attribute.substring(0, i++);
-            attr = attribute.substring(i);
-        }
-
-        setAttribute(path, view, attr, value, options);
-    }
-
-    public void setAttribute(Path path, String view, String attr, Object value, LinkOption... options) throws IOException {
-        SftpPath p = toSftpPath(path);
-        SftpFileSystem fs = p.getFileSystem();
-        Collection<String> views = fs.supportedFileAttributeViews();
-        if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
-            throw new UnsupportedOperationException("setAttribute(" + path + ")[" + view + ":" + attr + "=" + value + "] view " + view + " not supported: " + views);
-        }
-
-        SftpClient.Attributes attributes = new SftpClient.Attributes();
-        switch (attr) {
-            case "lastModifiedTime":
-                attributes.modifyTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
-                break;
-            case "lastAccessTime":
-                attributes.accessTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
-                break;
-            case "creationTime":
-                attributes.createTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
-                break;
-            case "size":
-                attributes.size(((Number) value).longValue());
-                break;
-            case "permissions": {
-                @SuppressWarnings("unchecked")
-                Set<PosixFilePermission> attrSet = (Set<PosixFilePermission>) value;
-                attributes.perms(attributesToPermissions(path, attrSet));
-                break;
-            }
-            case "owner":
-                attributes.owner(((UserPrincipal) value).getName());
-                break;
-            case "group":
-                attributes.group(((GroupPrincipal) value).getName());
-                break;
-            case "acl": {
-                ValidateUtils.checkTrue("acl".equalsIgnoreCase(view), "ACL cannot be set via view=%s", view);
-                @SuppressWarnings("unchecked")
-                List<AclEntry> acl = (List<AclEntry>) value;
-                attributes.acl(acl);
-                break;
-            }
-            case "isRegularFile":
-            case "isDirectory":
-            case "isSymbolicLink":
-            case "isOther":
-            case "fileKey":
-                throw new UnsupportedOperationException("setAttribute(" + path + ")[" + view + ":" + attr + "=" + value + "] modification N/A");
-            default:
-                if (log.isTraceEnabled()) {
-                    log.trace("setAttribute({})[{}] ignore {}:{}={}", fs, path, view, attr, value);
-                }
-        }
-
-        if (log.isDebugEnabled()) {
-            log.debug("setAttribute({}) {}: {}", fs, path, attributes);
-        }
-
-        try (SftpClient client = fs.getClient()) {
-            client.setStat(p.toString(), attributes);
-        }
-    }
-
-    public SftpPath toSftpPath(Path path) {
-        Objects.requireNonNull(path, "No path provided");
-        if (!(path instanceof SftpPath)) {
-            throw new ProviderMismatchException("Path is not SFTP: " + path);
-        }
-        return (SftpPath) path;
-    }
-
-    protected int attributesToPermissions(Path path, Collection<PosixFilePermission> perms) {
-        if (GenericUtils.isEmpty(perms)) {
-            return 0;
-        }
-
-        int pf = 0;
-        boolean traceEnabled = log.isTraceEnabled();
-        for (PosixFilePermission p : perms) {
-            switch (p) {
-                case OWNER_READ:
-                    pf |= SftpConstants.S_IRUSR;
-                    break;
-                case OWNER_WRITE:
-                    pf |= SftpConstants.S_IWUSR;
-                    break;
-                case OWNER_EXECUTE:
-                    pf |= SftpConstants.S_IXUSR;
-                    break;
-                case GROUP_READ:
-                    pf |= SftpConstants.S_IRGRP;
-                    break;
-                case GROUP_WRITE:
-                    pf |= SftpConstants.S_IWGRP;
-                    break;
-                case GROUP_EXECUTE:
-                    pf |= SftpConstants.S_IXGRP;
-                    break;
-                case OTHERS_READ:
-                    pf |= SftpConstants.S_IROTH;
-                    break;
-                case OTHERS_WRITE:
-                    pf |= SftpConstants.S_IWOTH;
-                    break;
-                case OTHERS_EXECUTE:
-                    pf |= SftpConstants.S_IXOTH;
-                    break;
-                default:
-                    if (traceEnabled) {
-                        log.trace("attributesToPermissions(" + path + ") ignored " + p);
-                    }
-            }
-        }
-
-        return pf;
-    }
-
-    public static String getRWXPermissions(int perms) {
-        StringBuilder sb = new StringBuilder(10 /* 3 * rwx + (d)irectory */);
-        if ((perms & SftpConstants.S_IFLNK) == SftpConstants.S_IFLNK) {
-            sb.append('l');
-        } else if ((perms & SftpConstants.S_IFDIR) == SftpConstants.S_IFDIR) {
-            sb.append('d');
-        } else {
-            sb.append('-');
-        }
-
-        if ((perms & SftpConstants.S_IRUSR) == SftpConstants.S_IRUSR) {
-            sb.append('r');
-        } else {
-            sb.append('-');
-        }
-        if ((perms & SftpConstants.S_IWUSR) == SftpConstants.S_IWUSR) {
-            sb.append('w');
-        } else {
-            sb.append('-');
-        }
-        if ((perms & SftpConstants.S_IXUSR) == SftpConstants.S_IXUSR) {
-            sb.append('x');
-        } else {
-            sb.append('-');
-        }
-
-        if ((perms & SftpConstants.S_IRGRP) == SftpConstants.S_IRGRP) {
-            sb.append('r');
-        } else {
-            sb.append('-');
-        }
-        if ((perms & SftpConstants.S_IWGRP) == SftpConstants.S_IWGRP) {
-            sb.append('w');
-        } else {
-            sb.append('-');
-        }
-        if ((perms & SftpConstants.S_IXGRP) == SftpConstants.S_IXGRP) {
-            sb.append('x');
-        } else {
-            sb.append('-');
-        }
-
-        if ((perms & SftpConstants.S_IROTH) == SftpConstants.S_IROTH) {
-            sb.append('r');
-        } else {
-            sb.append('-');
-        }
-        if ((perms & SftpConstants.S_IWOTH) == SftpConstants.S_IWOTH) {
-            sb.append('w');
-        } else {
-            sb.append('-');
-        }
-        if ((perms & SftpConstants.S_IXOTH) == SftpConstants.S_IXOTH) {
-            sb.append('x');
-        } else {
-            sb.append('-');
-        }
-
-        return sb.toString();
-    }
-
-    public static String getOctalPermissions(int perms) {
-        Collection<PosixFilePermission> attrs = permissionsToAttributes(perms);
-        return getOctalPermissions(attrs);
-    }
-
-    public static Set<PosixFilePermission> permissionsToAttributes(int perms) {
-        Set<PosixFilePermission> p = EnumSet.noneOf(PosixFilePermission.class);
-        if ((perms & SftpConstants.S_IRUSR) == SftpConstants.S_IRUSR) {
-            p.add(PosixFilePermission.OWNER_READ);
-        }
-        if ((perms & SftpConstants.S_IWUSR) == SftpConstants.S_IWUSR) {
-            p.add(PosixFilePermission.OWNER_WRITE);
-        }
-        if ((perms & SftpConstants.S_IXUSR) == SftpConstants.S_IXUSR) {
-            p.add(PosixFilePermission.OWNER_EXECUTE);
-        }
-        if ((perms & SftpConstants.S_IRGRP) == SftpConstants.S_IRGRP) {
-            p.add(PosixFilePermission.GROUP_READ);
-        }
-        if ((perms & SftpConstants.S_IWGRP) == SftpConstants.S_IWGRP) {
-            p.add(PosixFilePermission.GROUP_WRITE);
-        }
-        if ((perms & SftpConstants.S_IXGRP) == SftpConstants.S_IXGRP) {
-            p.add(PosixFilePermission.GROUP_EXECUTE);
-        }
-        if ((perms & SftpConstants.S_IROTH) == SftpConstants.S_IROTH) {
-            p.add(PosixFilePermission.OTHERS_READ);
-        }
-        if ((perms & SftpConstants.S_IWOTH) == SftpConstants.S_IWOTH) {
-            p.add(PosixFilePermission.OTHERS_WRITE);
-        }
-        if ((perms & SftpConstants.S_IXOTH) == SftpConstants.S_IXOTH) {
-            p.add(PosixFilePermission.OTHERS_EXECUTE);
-        }
-        return p;
-    }
-
-    public static String getOctalPermissions(Collection<PosixFilePermission> perms) {
-        int pf = 0;
-
-        for (PosixFilePermission p : perms) {
-            switch (p) {
-                case OWNER_READ:
-                    pf |= SftpConstants.S_IRUSR;
-                    break;
-                case OWNER_WRITE:
-                    pf |= SftpConstants.S_IWUSR;
-                    break;
-                case OWNER_EXECUTE:
-                    pf |= SftpConstants.S_IXUSR;
-                    break;
-                case GROUP_READ:
-                    pf |= SftpConstants.S_IRGRP;
-                    break;
-                case GROUP_WRITE:
-                    pf |= SftpConstants.S_IWGRP;
-                    break;
-                case GROUP_EXECUTE:
-                    pf |= SftpConstants.S_IXGRP;
-                    break;
-                case OTHERS_READ:
-                    pf |= SftpConstants.S_IROTH;
-                    break;
-                case OTHERS_WRITE:
-                    pf |= SftpConstants.S_IWOTH;
-                    break;
-                case OTHERS_EXECUTE:
-                    pf |= SftpConstants.S_IXOTH;
-                    break;
-                default:    // ignored
-            }
-        }
-
-        return String.format("%04o", pf);
-    }
-
-    /**
-     * Uses the host, port and username to create a unique identifier
-     *
-     * @param uri The {@link URI} - <B>Note:</B> not checked to make sure
-     *            that the scheme is {@code sftp://}
-     * @return The unique identifier
-     * @see #getFileSystemIdentifier(String, int, String)
-     */
-    public static String getFileSystemIdentifier(URI uri) {
-        String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided");
-        String[] ui = GenericUtils.split(userInfo, ':');
-        ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
-        return getFileSystemIdentifier(uri.getHost(), uri.getPort(), ui[0]);
-    }
-
-    /**
-     * Uses the remote host address, port and current username to create a unique identifier
-     *
-     * @param session The {@link ClientSession}
-     * @return The unique identifier
-     * @see #getFileSystemIdentifier(String, int, String)
-     */
-    public static String getFileSystemIdentifier(ClientSession session) {
-        IoSession ioSession = session.getIoSession();
-        SocketAddress addr = ioSession.getRemoteAddress();
-        String username = session.getUsername();
-        if (addr instanceof InetSocketAddress) {
-            InetSocketAddress inetAddr = (InetSocketAddress) addr;
-            return getFileSystemIdentifier(inetAddr.getHostString(), inetAddr.getPort(), username);
-        } else {
-            return getFileSystemIdentifier(addr.toString(), SshConstants.DEFAULT_PORT, username);
-        }
-    }
-
-    public static String getFileSystemIdentifier(String host, int port, String username) {
-        return GenericUtils.trimToEmpty(host) + ':'
-                + ((port <= 0) ? SshConstants.DEFAULT_PORT : port) + ':'
-                + GenericUtils.trimToEmpty(username);
-    }
-
-    public static URI createFileSystemURI(String host, int port, String username, String password) {
-        return createFileSystemURI(host, port, username, password, Collections.emptyMap());
-    }
-
-    public static URI createFileSystemURI(String host, int port, String username, String password, Map<String, ?> params) {
-        ValidateUtils.checkNotNullAndNotEmpty(host, "No host provided");
-
-        String queryPart = null;
-        int numParams = GenericUtils.size(params);
-        if (numParams > 0) {
-            StringBuilder sb = new StringBuilder(numParams * Short.SIZE);
-            for (Map.Entry<String, ?> pe : params.entrySet()) {
-                String key = pe.getKey();
-                Object value = pe.getValue();
-                if (sb.length() > 0) {
-                    sb.append('&');
-                }
-                sb.append(key);
-                if (value != null) {
-                    sb.append('=').append(Objects.toString(value, null));
-                }
-            }
-
-            queryPart = sb.toString();
-        }
-
-        try {
-            String userAuth = encodeCredentials(username, password);
-            return new URI(SftpConstants.SFTP_SUBSYSTEM_NAME, userAuth, host, port, "/", queryPart, null);
-        } catch (URISyntaxException e) {
-            throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ")"
-                    + " to create access URI: " + e.getMessage(), e);
-        }
-    }
-
-    public static String encodeCredentials(String username, String password) {
-        ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided");
-        ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided");
-        /*
-         * There is no way to properly encode/decode credentials that already contain
-         * colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1:
-         *
-         *
-         *      Use of the format "user:password" in the userinfo field is
-         *      deprecated.  Applications should not render as clear text any data
-         *      after the first colon (":") character found within a userinfo
-         *      subcomponent unless the data after the colon is the empty string
-         *      (indicating no password).  Applications may choose to ignore or
-         *      reject such data when it is received as part of a reference and
-         *      should reject the storage of such data in unencrypted form.
-         */
-        ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials");
-        return username + ":" + password;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
deleted file mode 100644
index 5567b58..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.nio.file.LinkOption;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.List;
-
-import org.apache.sshd.common.file.util.BasePath;
-
-public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
-    public SftpPath(SftpFileSystem fileSystem, String root, List<String> names) {
-        super(fileSystem, root, names);
-    }
-
-    @Override
-    public SftpPath toRealPath(LinkOption... options) throws IOException {
-        // TODO: handle links
-        SftpPath absolute = toAbsolutePath();
-        FileSystem fs = getFileSystem();
-        FileSystemProvider provider = fs.provider();
-        provider.checkAccess(absolute);
-        return absolute;
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
deleted file mode 100644
index 49b4b48..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.nio.file.Path;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpPathIterator implements Iterator<Path> {
-    private final SftpPath p;
-    private final Iterator<? extends SftpClient.DirEntry> it;
-    private boolean dotIgnored;
-    private boolean dotdotIgnored;
-    private SftpClient.DirEntry curEntry;
-
-    public SftpPathIterator(SftpPath path, Iterable<? extends SftpClient.DirEntry> iter) {
-        this(path, (iter == null) ? null : iter.iterator());
-    }
-
-    public SftpPathIterator(SftpPath path, Iterator<? extends SftpClient.DirEntry> iter) {
-        p = path;
-        it = iter;
-        curEntry = nextEntry();
-    }
-
-    @Override
-    public boolean hasNext() {
-        return curEntry != null;
-    }
-
-    @Override
-    public Path next() {
-        if (curEntry == null) {
-            throw new NoSuchElementException("No next entry");
-        }
-
-        SftpClient.DirEntry entry = curEntry;
-        curEntry = nextEntry();
-        return p.resolve(entry.getFilename());
-    }
-
-    private SftpClient.DirEntry nextEntry() {
-        while ((it != null) && it.hasNext()) {
-            SftpClient.DirEntry entry = it.next();
-            String name = entry.getFilename();
-            if (".".equals(name) && (!dotIgnored)) {
-                dotIgnored = true;
-            } else if ("..".equals(name) && (!dotdotIgnored)) {
-                dotdotIgnored = true;
-            } else {
-                return entry;
-            }
-        }
-
-        return null;
-    }
-
-    @Override
-    public void remove() {
-        throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
deleted file mode 100644
index 1fb614c..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.util.Set;
-
-import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
-import org.apache.sshd.common.util.GenericUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpPosixFileAttributeView extends AbstractSftpFileAttributeView implements PosixFileAttributeView {
-    public SftpPosixFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
-        super(provider, path, options);
-    }
-
-    @Override
-    public String name() {
-        return "posix";
-    }
-
-    @Override
-    public PosixFileAttributes readAttributes() throws IOException {
-        return new SftpPosixFileAttributes(path, readRemoteAttributes());
-    }
-
-    @Override
-    public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
-        SftpClient.Attributes attrs = new SftpClient.Attributes();
-        if (lastModifiedTime != null) {
-            attrs.modifyTime(lastModifiedTime);
-        }
-        if (lastAccessTime != null) {
-            attrs.accessTime(lastAccessTime);
-        }
-        if (createTime != null) {
-            attrs.createTime(createTime);
-        }
-
-        if (GenericUtils.isEmpty(attrs.getFlags())) {
-            if (log.isDebugEnabled()) {
-                log.debug("setTimes({}) no changes", path);
-            }
-        } else {
-            writeRemoteAttributes(attrs);
-        }
-    }
-
-    @Override
-    public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
-        provider.setAttribute(path, "permissions", perms, options);
-    }
-
-    @Override
-    public void setGroup(GroupPrincipal group) throws IOException {
-        provider.setAttribute(path, "group", group, options);
-    }
-
-    @Override
-    public UserPrincipal getOwner() throws IOException {
-        return readAttributes().owner();
-    }
-
-    @Override
-    public void setOwner(UserPrincipal owner) throws IOException {
-        provider.setAttribute(path, "owner", owner, options);
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
deleted file mode 100644
index a07e67f..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.client.subsystem.sftp;
-
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.util.Set;
-
-import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
-import org.apache.sshd.common.util.GenericUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpPosixFileAttributes implements PosixFileAttributes {
-    private final Path path;
-    private final Attributes attributes;
-
-    public SftpPosixFileAttributes(Path path, Attributes attributes) {
-        this.path = path;
-        this.attributes = attributes;
-    }
-
-    /**
-     * @return The referenced attributes file {@link Path}
-     */
-    public final Path getPath() {
-        return path;
-    }
-
-    @Override
-    public UserPrincipal owner() {
-        String owner = attributes.getOwner();
-        return GenericUtils.isEmpty(owner) ? null : new SftpFileSystem.DefaultUserPrincipal(owner);
-    }
-
-    @Override
-    public GroupPrincipal group() {
-        String group = attributes.getGroup();
-        return GenericUtils.isEmpty(group) ? null : new SftpFileSystem.DefaultGroupPrincipal(group);
-    }
-
-    @Override
-    public Set<PosixFilePermission> permissions() {
-        return SftpFileSystemProvider.permissionsToAttributes(attributes.getPermissions());
-    }
-
-    @Override
-    public FileTime lastModifiedTime() {
-        return attributes.getModifyTime();
-    }
-
-    @Override
-    public FileTime lastAccessTime() {
-        return attributes.getAccessTime();
-    }
-
-    @Override
-    public FileTime creationTime() {
-        return attributes.getCreateTime();
-    }
-
-    @Override
-    public boolean isRegularFile() {
-        return attributes.isRegularFile();
-    }
-
-    @Override
-    public boolean isDirectory() {
-        return attributes.isDirectory();
-    }
-
-    @Override
-    public boolean isSymbolicLink() {
-        return attributes.isSymbolicLink();
-    }
-
-    @Override
-    public boolean isOther() {
-        return attributes.isOther();
-    }
-
-    @Override
-    public long size() {
-        return attributes.getSize();
-    }
-
-    @Override
-    public Object fileKey() {
-        // TODO consider implementing this
-        return null;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpAclFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpAclFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpAclFileAttributeView.java
new file mode 100644
index 0000000..fee19b0
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpAclFileAttributeView.java
@@ -0,0 +1,68 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.List;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpAclFileAttributeView extends AbstractSftpFileAttributeView implements AclFileAttributeView {
+    public SftpAclFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
+        super(provider, path, options);
+    }
+
+    @Override
+    public UserPrincipal getOwner() throws IOException {
+        PosixFileAttributes v = provider.readAttributes(path, PosixFileAttributes.class, options);
+        return v.owner();
+    }
+
+    @Override
+    public void setOwner(UserPrincipal owner) throws IOException {
+        provider.setAttribute(path, "posix", "owner", owner, options);
+    }
+
+    @Override
+    public String name() {
+        return "acl";
+    }
+
+    @Override
+    public List<AclEntry> getAcl() throws IOException {
+        return readRemoteAttributes().getAcl();
+    }
+
+    @Override
+    public void setAcl(List<AclEntry> acl) throws IOException {
+        writeRemoteAttributes(new SftpClient.Attributes().acl(acl));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpDirectoryStream.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpDirectoryStream.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpDirectoryStream.java
new file mode 100644
index 0000000..f250af5
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpDirectoryStream.java
@@ -0,0 +1,67 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+
+/**
+ * Implements a remote {@link DirectoryStream}
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpDirectoryStream implements DirectoryStream<Path> {
+    private final SftpClient sftp;
+    private final Iterable<SftpClient.DirEntry> iter;
+    private final SftpPath p;
+
+    /**
+     * @param path The remote {@link SftpPath}
+     * @throws IOException If failed to initialize the directory access handle
+     */
+    public SftpDirectoryStream(SftpPath path) throws IOException {
+        SftpFileSystem fs = path.getFileSystem();
+        p = path;
+        sftp = fs.getClient();
+        iter = sftp.readDir(path.toString());
+    }
+
+    /**
+     * Client instance used to access the remote directory
+     *
+     * @return The {@link SftpClient} instance used to access the remote directory
+     */
+    public final SftpClient getClient() {
+        return sftp;
+    }
+
+    @Override
+    public Iterator<Path> iterator() {
+        return new SftpPathIterator(p, iter);
+    }
+
+    @Override
+    public void close() throws IOException {
+        sftp.close();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileStore.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileStore.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileStore.java
new file mode 100644
index 0000000..a909e2d
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileStore.java
@@ -0,0 +1,105 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+import java.util.Collection;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileStore extends FileStore {
+    private final SftpFileSystem fs;
+    private final String name;
+
+    public SftpFileStore(String name, SftpFileSystem fs) {
+        this.name = name;
+        this.fs = fs;
+    }
+
+    public final SftpFileSystem getFileSystem() {
+        return fs;
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public String type() {
+        return SftpConstants.SFTP_SUBSYSTEM_NAME;
+    }
+
+    @Override
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    @Override
+    public long getTotalSpace() throws IOException {
+        return Long.MAX_VALUE;  // TODO use SFTPv6 space-available extension
+    }
+
+    @Override
+    public long getUsableSpace() throws IOException {
+        return Long.MAX_VALUE;
+    }
+
+    @Override
+    public long getUnallocatedSpace() throws IOException {
+        return Long.MAX_VALUE;
+    }
+
+    @Override
+    public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
+        SftpFileSystem sftpFs = getFileSystem();
+        SftpFileSystemProvider provider = sftpFs.provider();
+        return provider.isSupportedFileAttributeView(sftpFs, type);
+    }
+
+    @Override
+    public boolean supportsFileAttributeView(String name) {
+        if (GenericUtils.isEmpty(name)) {
+            return false;   // debug breakpoint
+        }
+
+        FileSystem sftpFs = getFileSystem();
+        Collection<String> views = sftpFs.supportedFileAttributeViews();
+        return !GenericUtils.isEmpty(views) && views.contains(name);
+    }
+
+    @Override
+    public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
+        return null;    // no special views supported
+    }
+
+    @Override
+    public Object getAttribute(String attribute) throws IOException {
+        return null;    // no special attributes supported
+    }
+}


[4/8] mina-sshd git commit: [SSHD-861] Added SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider

Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
new file mode 100644
index 0000000..fdec2f3
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
@@ -0,0 +1,1340 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.FileSystemException;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileOwnerAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.auth.BasicCredentialsImpl;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+import org.apache.sshd.common.auth.MutableBasicCredentials;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A registered {@link FileSystemProvider} that registers the &quot;sftp://&quot;
+ * scheme so that URLs with this protocol are handled as remote SFTP {@link Path}-s
+ * - e.g., &quot;{@code sftp://user:password@host/remote/file/path}&quot;
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileSystemProvider extends FileSystemProvider {
+    public static final String READ_BUFFER_PROP_NAME = "sftp-fs-read-buffer-size";
+    public static final int DEFAULT_READ_BUFFER_SIZE = SftpClient.DEFAULT_READ_BUFFER_SIZE;
+    public static final String WRITE_BUFFER_PROP_NAME = "sftp-fs-write-buffer-size";
+    public static final int DEFAULT_WRITE_BUFFER_SIZE = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
+    public static final String CONNECT_TIME_PROP_NAME = "sftp-fs-connect-time";
+    public static final long DEFAULT_CONNECT_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
+    public static final String AUTH_TIME_PROP_NAME = "sftp-fs-auth-time";
+    public static final long DEFAULT_AUTH_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
+    public static final String NAME_DECORDER_CHARSET_PROP_NAME = "sftp-fs-name-decoder-charset";
+    public static final Charset DEFAULT_NAME_DECODER_CHARSET = SftpClient.DEFAULT_NAME_DECODING_CHARSET;
+
+    /**
+     * <P>
+     * URI parameter that can be used to specify a special version selection. Options are:
+     * </P>
+     * <UL>
+     *      <LI>{@code max} - select maximum available version for the client</LI>
+     *      <LI>{@code min} - select minimum available version for the client</LI>
+     *      <LI>{@code current} - whatever version is reported by the server</LI>
+     *      <LI>{@code nnn} - select <U>only</U> the specified version</LI>
+     *      <LI>{@code a,b,c} - select one of the specified versions (if available) in preference order</LI>
+     * </UL>
+     */
+    public static final String VERSION_PARAM = "version";
+
+    public static final Set<Class<? extends FileAttributeView>> UNIVERSAL_SUPPORTED_VIEWS =
+        Collections.unmodifiableSet(
+            GenericUtils.asSet(
+                PosixFileAttributeView.class,
+                FileOwnerAttributeView.class,
+                BasicFileAttributeView.class
+            ));
+
+    protected final Logger log;
+
+    private final SshClient clientInstance;
+    private final SftpClientFactory factory;
+    private final SftpVersionSelector versionSelector;
+    private final NavigableMap<String, SftpFileSystem> fileSystems = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    private SftpFileSystemClientSessionInitializer fsSessionInitializer = SftpFileSystemClientSessionInitializer.DEFAULT;
+
+    public SftpFileSystemProvider() {
+        this((SshClient) null);
+    }
+
+    public SftpFileSystemProvider(SftpVersionSelector selector) {
+        this(null, selector);
+    }
+
+    /**
+     * @param client The {@link SshClient} to use - if {@code null} then a
+     * default one will be setup and started. Otherwise, it is assumed that
+     * the client has already been started
+     * @see SshClient#setUpDefaultClient()
+     */
+    public SftpFileSystemProvider(SshClient client) {
+        this(client, SftpVersionSelector.CURRENT);
+    }
+
+    public SftpFileSystemProvider(SshClient client, SftpVersionSelector selector) {
+        this(client, null, selector);
+    }
+
+    public SftpFileSystemProvider(SshClient client, SftpClientFactory factory, SftpVersionSelector selector) {
+        this.log = LoggerFactory.getLogger(getClass());
+        this.factory = factory;
+        this.versionSelector = selector;
+        if (client == null) {
+            // TODO: make this configurable using system properties
+            client = SshClient.setUpDefaultClient();
+            client.start();
+        }
+        this.clientInstance = client;
+    }
+
+    @Override
+    public String getScheme() {
+        return SftpConstants.SFTP_SUBSYSTEM_NAME;
+    }
+
+    public final SftpVersionSelector getSftpVersionSelector() {
+        return versionSelector;
+    }
+
+    public final SshClient getClientInstance() {
+        return clientInstance;
+    }
+
+    public SftpClientFactory getSftpClientFactory() {
+        return factory;
+    }
+
+    public SftpFileSystemClientSessionInitializer getSftpFileSystemClientSessionInitializer() {
+        return fsSessionInitializer;
+    }
+
+    public void setSftpFileSystemClientSessionInitializer(SftpFileSystemClientSessionInitializer initializer) {
+        fsSessionInitializer = Objects.requireNonNull(initializer, "No initializer provided");
+    }
+
+    @Override // NOTE: co-variant return
+    public SftpFileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+        String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided");
+        int port = uri.getPort();
+        if (port <= 0) {
+            port = SshConstants.DEFAULT_PORT;
+        }
+
+        BasicCredentialsProvider credentials = parseCredentials(uri);
+        ValidateUtils.checkState(credentials != null, "No credentials provided");
+
+        String username = credentials.getUsername();
+        String id = getFileSystemIdentifier(host, port, username);
+        SftpFileSystemInitializationContext context = new SftpFileSystemInitializationContext(id, uri, env);
+        context.setHost(host);
+        context.setPort(port);
+        context.setCredentials(credentials);
+
+        Map<String, Object> params = resolveFileSystemParameters(env, parseURIParameters(uri));
+        PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(params);
+        context.setPropertyResolver(resolver);
+        context.setMaxConnectTime(resolver.getLongProperty(CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME));
+        context.setMaxAuthTime(resolver.getLongProperty(AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME));
+
+        SftpVersionSelector selector = resolveSftpVersionSelector(uri, getSftpVersionSelector(), resolver);
+        Charset decodingCharset = PropertyResolverUtils.getCharset(
+            resolver, NAME_DECORDER_CHARSET_PROP_NAME, DEFAULT_NAME_DECODER_CHARSET);
+
+        SftpFileSystemClientSessionInitializer initializer = getSftpFileSystemClientSessionInitializer();
+        SftpFileSystem fileSystem;
+        synchronized (fileSystems) {
+            if (fileSystems.containsKey(id)) {
+                throw new FileSystemAlreadyExistsException(id);
+            }
+
+            // TODO try and find a way to avoid doing this while locking the file systems cache
+            ClientSession session = null;
+            try {
+                session = initializer.createClientSession(this, context);
+
+                // Make any extra configuration parameters available to the session
+                if (GenericUtils.size(params) > 0) {
+                    // Cannot use forEach because the session is not effectively final
+                    for (Map.Entry<String, ?> pe : params.entrySet()) {
+                        String key = pe.getKey();
+                        Object value = pe.getValue();
+                        if (VERSION_PARAM.equalsIgnoreCase(key)) {
+                            continue;
+                        }
+
+                        PropertyResolverUtils.updateProperty(session, key, value);
+                    }
+
+                    PropertyResolverUtils.updateProperty(session, SftpClient.NAME_DECODING_CHARSET, decodingCharset);
+                }
+
+                initializer.authenticateClientSession(this, context, session);
+
+                fileSystem = initializer.createSftpFileSystem(this, context, session, selector);
+                fileSystems.put(id, fileSystem);
+            } catch (Exception e) {
+                if (session != null) {
+                    try {
+                        session.close();
+                    } catch (IOException t) {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Failed (" + t.getClass().getSimpleName() + ")"
+                                    + " to close session for new file system on " + host + ":" + port
+                                    + " due to " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]"
+                                    + ": " + t.getMessage());
+                        }
+                    }
+                }
+
+                if (e instanceof IOException) {
+                    throw (IOException) e;
+                } else if (e instanceof RuntimeException) {
+                    throw (RuntimeException) e;
+                } else {
+                    throw new IOException(e);
+                }
+            }
+        }
+
+        fileSystem.setReadBufferSize(resolver.getIntProperty(READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
+        fileSystem.setWriteBufferSize(resolver.getIntProperty(WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
+        if (log.isDebugEnabled()) {
+            log.debug("newFileSystem({}): {}", uri.toASCIIString(), fileSystem);
+        }
+        return fileSystem;
+    }
+
+    protected SftpVersionSelector resolveSftpVersionSelector(URI uri, SftpVersionSelector defaultSelector, PropertyResolver resolver) {
+        String preference = resolver.getString(VERSION_PARAM);
+        if (GenericUtils.isEmpty(preference)) {
+            return defaultSelector;
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("resolveSftpVersionSelector({}) preference={}", uri, preference);
+        }
+
+        if ("max".equalsIgnoreCase(preference)) {
+            return SftpVersionSelector.MAXIMUM;
+        } else if ("min".equalsIgnoreCase(preference)) {
+            return SftpVersionSelector.MINIMUM;
+        } else if ("current".equalsIgnoreCase(preference)) {
+            return SftpVersionSelector.CURRENT;
+        }
+
+        String[] values = GenericUtils.split(preference, ',');
+        if (values.length == 1) {
+            return SftpVersionSelector.fixedVersionSelector(Integer.parseInt(values[0]));
+        }
+
+        int[] preferred = new int[values.length];
+        for (int index = 0; index < values.length; index++) {
+            preferred[index] = Integer.parseInt(values[index]);
+        }
+
+        return SftpVersionSelector.preferredVersionSelector(preferred);
+    }
+
+    // NOTE: URI parameters override environment ones
+    public static Map<String, Object> resolveFileSystemParameters(Map<String, ?> env, Map<String, Object> uriParams) {
+        if (GenericUtils.isEmpty(env)) {
+            return GenericUtils.isEmpty(uriParams) ? Collections.emptyMap() : uriParams;
+        } else if (GenericUtils.isEmpty(uriParams)) {
+            return Collections.unmodifiableMap(env);
+        }
+
+        Map<String, Object> resolved = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        resolved.putAll(env);
+        resolved.putAll(uriParams);
+        return resolved;
+    }
+
+    /**
+     * Attempts to parse the user information from the URI
+     *
+     * @param uri The {@link URI} value - ignored if {@code null} or does not
+     * contain any {@link URI#getUserInfo() user info}.
+     * @return The parsed credentials - {@code null} if none available
+     */
+    public static MutableBasicCredentials parseCredentials(URI uri) {
+        return parseCredentials((uri == null) ? "" : uri.getUserInfo());
+    }
+
+    public static MutableBasicCredentials parseCredentials(String userInfo) {
+        if (GenericUtils.isEmpty(userInfo)) {
+            return null;
+        }
+
+        String[] ui = GenericUtils.split(userInfo, ':');
+        ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
+        return new BasicCredentialsImpl(ui[0], ui[1]);
+    }
+
+    public static Map<String, Object> parseURIParameters(URI uri) {
+        return parseURIParameters((uri == null) ? "" : uri.getQuery());
+    }
+
+    public static Map<String, Object> parseURIParameters(String params) {
+        if (GenericUtils.isEmpty(params)) {
+            return Collections.emptyMap();
+        }
+
+        if (params.charAt(0) == '?') {
+            if (params.length() == 1) {
+                return Collections.emptyMap();
+            }
+            params = params.substring(1);
+        }
+
+        String[] pairs = GenericUtils.split(params, '&');
+        Map<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        for (String p : pairs) {
+            int pos = p.indexOf('=');
+            if (pos < 0) {
+                map.put(p, Boolean.TRUE);
+                continue;
+            }
+
+            String key = p.substring(0, pos);
+            String value = p.substring(pos + 1);
+            if (NumberUtils.isIntegerNumber(value)) {
+                map.put(key, Long.valueOf(value));
+            } else if ("true".equals(value) || "false".equals("value")) {
+                map.put(key, Boolean.valueOf(value));
+            } else {
+                map.put(key, value);
+            }
+        }
+
+        return map;
+    }
+
+    public SftpFileSystem newFileSystem(ClientSession session) throws IOException {
+        String id = getFileSystemIdentifier(session);
+        SftpFileSystem fileSystem;
+        synchronized (fileSystems) {
+            if (fileSystems.containsKey(id)) {
+                throw new FileSystemAlreadyExistsException(id);
+            }
+            fileSystem = new SftpFileSystem(this, id, session, factory, getSftpVersionSelector());
+            fileSystems.put(id, fileSystem);
+        }
+
+        fileSystem.setReadBufferSize(session.getIntProperty(READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
+        fileSystem.setWriteBufferSize(session.getIntProperty(WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
+        if (log.isDebugEnabled()) {
+            log.debug("newFileSystem: {}", fileSystem);
+        }
+
+        return fileSystem;
+    }
+
+    @Override
+    public FileSystem getFileSystem(URI uri) {
+        String id = getFileSystemIdentifier(uri);
+        SftpFileSystem fs = getFileSystem(id);
+        if (fs == null) {
+            throw new FileSystemNotFoundException(id);
+        }
+        return fs;
+    }
+
+    /**
+     * @param id File system identifier - ignored if {@code null}/empty
+     * @return The removed {@link SftpFileSystem} - {@code null} if no match
+     */
+    public SftpFileSystem removeFileSystem(String id) {
+        if (GenericUtils.isEmpty(id)) {
+            return null;
+        }
+
+        SftpFileSystem removed;
+        synchronized (fileSystems) {
+            removed = fileSystems.remove(id);
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("removeFileSystem({}): {}", id, removed);
+        }
+        return removed;
+    }
+
+    /**
+     * @param id File system identifier - ignored if {@code null}/empty
+     * @return The cached {@link SftpFileSystem} - {@code null} if no match
+     */
+    public SftpFileSystem getFileSystem(String id) {
+        if (GenericUtils.isEmpty(id)) {
+            return null;
+        }
+
+        synchronized (fileSystems) {
+            return fileSystems.get(id);
+        }
+    }
+
+    @Override
+    public Path getPath(URI uri) {
+        FileSystem fs = getFileSystem(uri);
+        return fs.getPath(uri.getPath());
+    }
+
+    @Override
+    public FileChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+        return newFileChannel(path, options, attrs);
+    }
+
+    @Override
+    public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+        Collection<SftpClient.OpenMode> modes = SftpClient.OpenMode.fromOpenOptions(options);
+        if (modes.isEmpty()) {
+            modes = EnumSet.of(SftpClient.OpenMode.Read, SftpClient.OpenMode.Write);
+        }
+        // TODO: process file attributes
+        return new SftpFileSystemChannel(toSftpPath(path), modes);
+    }
+
+    @Override
+    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
+        final SftpPath p = toSftpPath(dir);
+        return new SftpDirectoryStream(p);
+    }
+
+    @Override
+    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+        SftpPath p = toSftpPath(dir);
+        SftpFileSystem fs = p.getFileSystem();
+        if (log.isDebugEnabled()) {
+            log.debug("createDirectory({}) {} ({})", fs, dir, Arrays.asList(attrs));
+        }
+        try (SftpClient sftp = fs.getClient()) {
+            try {
+                sftp.mkdir(dir.toString());
+            } catch (SftpException e) {
+                int sftpStatus = e.getStatus();
+                if ((sftp.getVersion() == SftpConstants.SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
+                    try {
+                        Attributes attributes = sftp.stat(dir.toString());
+                        if (attributes != null) {
+                            throw new FileAlreadyExistsException(p.toString());
+                        }
+                    } catch (SshException e2) {
+                        e.addSuppressed(e2);
+                    }
+                }
+                if (sftpStatus == SftpConstants.SSH_FX_FILE_ALREADY_EXISTS) {
+                    throw new FileAlreadyExistsException(p.toString());
+                }
+                throw e;
+            }
+            for (FileAttribute<?> attr : attrs) {
+                setAttribute(p, attr.name(), attr.value());
+            }
+        }
+    }
+
+    @Override
+    public void delete(Path path) throws IOException {
+        SftpPath p = toSftpPath(path);
+        checkAccess(p, AccessMode.WRITE);
+
+        SftpFileSystem fs = p.getFileSystem();
+        if (log.isDebugEnabled()) {
+            log.debug("delete({}) {}", fs, path);
+        }
+
+        try (SftpClient sftp = fs.getClient()) {
+            BasicFileAttributes attributes = readAttributes(path, BasicFileAttributes.class);
+            if (attributes.isDirectory()) {
+                sftp.rmdir(path.toString());
+            } else {
+                sftp.remove(path.toString());
+            }
+        }
+    }
+
+    @Override
+    public void copy(Path source, Path target, CopyOption... options) throws IOException {
+        SftpPath src = toSftpPath(source);
+        SftpPath dst = toSftpPath(target);
+        if (src.getFileSystem() != dst.getFileSystem()) {
+            throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
+        }
+        checkAccess(src);
+
+        boolean replaceExisting = false;
+        boolean copyAttributes = false;
+        boolean noFollowLinks = false;
+        for (CopyOption opt : options) {
+            replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
+            copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
+            noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
+        }
+        LinkOption[] linkOptions = IoUtils.getLinkOptions(!noFollowLinks);
+
+        // attributes of source file
+        BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
+        if (attrs.isSymbolicLink()) {
+            throw new IOException("Copying of symbolic links not supported");
+        }
+
+        // delete target if it exists and REPLACE_EXISTING is specified
+        Boolean status = IoUtils.checkFileExists(target, linkOptions);
+        if (status == null) {
+            throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("copy({})[{}] {} => {}", src.getFileSystem(), Arrays.asList(options), src, dst);
+        }
+
+        if (replaceExisting) {
+            deleteIfExists(target);
+        } else {
+            if (status) {
+                throw new FileAlreadyExistsException(target.toString());
+            }
+        }
+
+        // create directory or copy file
+        if (attrs.isDirectory()) {
+            createDirectory(target);
+        } else {
+            try (InputStream in = newInputStream(source);
+                 OutputStream os = newOutputStream(target)) {
+                IoUtils.copy(in, os);
+            }
+        }
+
+        // copy basic attributes to target
+        if (copyAttributes) {
+            BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
+            try {
+                view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
+            } catch (Throwable x) {
+                // rollback
+                try {
+                    delete(target);
+                } catch (Throwable suppressed) {
+                    x.addSuppressed(suppressed);
+                }
+                throw x;
+            }
+        }
+    }
+
+    @Override
+    public void move(Path source, Path target, CopyOption... options) throws IOException {
+        SftpPath src = toSftpPath(source);
+        SftpFileSystem fsSrc = src.getFileSystem();
+        SftpPath dst = toSftpPath(target);
+
+        if (src.getFileSystem() != dst.getFileSystem()) {
+            throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
+        }
+        checkAccess(src);
+
+        boolean replaceExisting = false;
+        boolean copyAttributes = false;
+        boolean noFollowLinks = false;
+        for (CopyOption opt : options) {
+            replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
+            copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
+            noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
+        }
+        LinkOption[] linkOptions = IoUtils.getLinkOptions(noFollowLinks);
+
+        // attributes of source file
+        BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
+        if (attrs.isSymbolicLink()) {
+            throw new IOException("Moving of source symbolic link (" + source + ") to " + target + " not supported");
+        }
+
+        // delete target if it exists and REPLACE_EXISTING is specified
+        Boolean status = IoUtils.checkFileExists(target, linkOptions);
+        if (status == null) {
+            throw new AccessDeniedException("Existence cannot be determined for move target " + target);
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("move({})[{}] {} => {}", src.getFileSystem(), Arrays.asList(options), src, dst);
+        }
+
+        if (replaceExisting) {
+            deleteIfExists(target);
+        } else if (status) {
+            throw new FileAlreadyExistsException(target.toString());
+        }
+
+        try (SftpClient sftp = fsSrc.getClient()) {
+            sftp.rename(src.toString(), dst.toString());
+        }
+
+        // copy basic attributes to target
+        if (copyAttributes) {
+            BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
+            try {
+                view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
+            } catch (Throwable x) {
+                // rollback
+                try {
+                    delete(target);
+                } catch (Throwable suppressed) {
+                    x.addSuppressed(suppressed);
+                }
+                throw x;
+            }
+        }
+    }
+
+    @Override
+    public boolean isSameFile(Path path1, Path path2) throws IOException {
+        SftpPath p1 = toSftpPath(path1);
+        SftpPath p2 = toSftpPath(path2);
+        if (p1.getFileSystem() != p2.getFileSystem()) {
+            throw new ProviderMismatchException("Mismatched file system providers for " + p1 + " vs. " + p2);
+        }
+        checkAccess(p1);
+        checkAccess(p2);
+        return p1.equals(p2);
+    }
+
+    @Override
+    public boolean isHidden(Path path) throws IOException {
+        return false;
+    }
+
+    @Override
+    public FileStore getFileStore(Path path) throws IOException {
+        FileSystem fs = path.getFileSystem();
+        if (!(fs instanceof SftpFileSystem)) {
+            throw new FileSystemException(path.toString(), path.toString(), "getFileStore(" + path + ") path not attached to an SFTP file system");
+        }
+
+        SftpFileSystem sftpFs = (SftpFileSystem) fs;
+        String id = sftpFs.getId();
+        SftpFileSystem cached = getFileSystem(id);
+        if (cached != sftpFs) {
+            throw new FileSystemException(path.toString(), path.toString(), "Mismatched file system instance for id=" + id);
+        }
+
+        return sftpFs.getFileStores().get(0);
+    }
+
+    @Override
+    public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
+        SftpPath l = toSftpPath(link);
+        SftpFileSystem fsLink = l.getFileSystem();
+        SftpPath t = toSftpPath(target);
+        if (fsLink != t.getFileSystem()) {
+            throw new ProviderMismatchException("Mismatched file system providers for " + l + " vs. " + t);
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("createSymbolicLink({})[{}] {} => {}", fsLink, Arrays.asList(attrs), link, target);
+        }
+
+        try (SftpClient client = fsLink.getClient()) {
+            client.symLink(l.toString(), t.toString());
+        }
+    }
+
+    @Override
+    public Path readSymbolicLink(Path link) throws IOException {
+        SftpPath l = toSftpPath(link);
+        SftpFileSystem fsLink = l.getFileSystem();
+        try (SftpClient client = fsLink.getClient()) {
+            String linkPath = client.readLink(l.toString());
+            if (log.isDebugEnabled()) {
+                log.debug("readSymbolicLink({})[{}] {} => {}", fsLink, link, linkPath);
+            }
+
+            return fsLink.getPath(linkPath);
+        }
+    }
+
+    @Override
+    public void checkAccess(Path path, AccessMode... modes) throws IOException {
+        SftpPath p = toSftpPath(path);
+        boolean w = false;
+        boolean x = false;
+        if (GenericUtils.length(modes) > 0) {
+            for (AccessMode mode : modes) {
+                switch (mode) {
+                    case READ:
+                        break;
+                    case WRITE:
+                        w = true;
+                        break;
+                    case EXECUTE:
+                        x = true;
+                        break;
+                    default:
+                        throw new UnsupportedOperationException("Unsupported mode: " + mode);
+                }
+            }
+        }
+
+        BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
+        if ((attrs == null) && !(p.isAbsolute() && p.getNameCount() == 0)) {
+            throw new NoSuchFileException(path.toString());
+        }
+
+        SftpFileSystem fs = p.getFileSystem();
+        if (x || (w && fs.isReadOnly())) {
+            throw new AccessDeniedException("Filesystem is read-only: " + path.toString());
+        }
+    }
+
+    @Override
+    public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, final LinkOption... options) {
+        if (isSupportedFileAttributeView(path, type)) {
+            if (AclFileAttributeView.class.isAssignableFrom(type)) {
+                return type.cast(new SftpAclFileAttributeView(this, path, options));
+            } else if (BasicFileAttributeView.class.isAssignableFrom(type)) {
+                return type.cast(new SftpPosixFileAttributeView(this, path, options));
+            }
+        }
+
+        throw new UnsupportedOperationException("getFileAttributeView(" + path + ") view not supported: " + type.getSimpleName());
+    }
+
+    public boolean isSupportedFileAttributeView(Path path, Class<? extends FileAttributeView> type) {
+        return isSupportedFileAttributeView(toSftpPath(path).getFileSystem(), type);
+    }
+
+    public boolean isSupportedFileAttributeView(SftpFileSystem fs, Class<? extends FileAttributeView> type) {
+        Collection<String> views = fs.supportedFileAttributeViews();
+        if ((type == null) || GenericUtils.isEmpty(views)) {
+            return false;
+        } else if (PosixFileAttributeView.class.isAssignableFrom(type)) {
+            return views.contains("posix");
+        } else if (AclFileAttributeView.class.isAssignableFrom(type)) {
+            return views.contains("acl");   // must come before owner view
+        } else if (FileOwnerAttributeView.class.isAssignableFrom(type)) {
+            return views.contains("owner");
+        } else if (BasicFileAttributeView.class.isAssignableFrom(type)) {
+            return views.contains("basic"); // must be last
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
+        if (type.isAssignableFrom(PosixFileAttributes.class)) {
+            return type.cast(getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes());
+        }
+
+        throw new UnsupportedOperationException("readAttributes(" + path + ")[" + type.getSimpleName() + "] N/A");
+    }
+
+    @Override
+    public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+        String view;
+        String attrs;
+        int i = attributes.indexOf(':');
+        if (i == -1) {
+            view = "basic";
+            attrs = attributes;
+        } else {
+            view = attributes.substring(0, i++);
+            attrs = attributes.substring(i);
+        }
+
+        return readAttributes(path, view, attrs, options);
+    }
+
+    public Map<String, Object> readAttributes(Path path, String view, String attrs, LinkOption... options) throws IOException {
+        SftpPath p = toSftpPath(path);
+        SftpFileSystem fs = p.getFileSystem();
+        Collection<String> views = fs.supportedFileAttributeViews();
+        if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
+            throw new UnsupportedOperationException("readAttributes(" + path + ")[" + view + ":" + attrs + "] view not supported: " + views);
+        }
+
+        if ("basic".equalsIgnoreCase(view) || "posix".equalsIgnoreCase(view) || "owner".equalsIgnoreCase(view)) {
+            return readPosixViewAttributes(p, view, attrs, options);
+        } else if ("acl".equalsIgnoreCase(view)) {
+            return readAclViewAttributes(p, view, attrs, options);
+        } else  {
+            return readCustomViewAttributes(p, view, attrs, options);
+        }
+    }
+
+    protected Map<String, Object> readCustomViewAttributes(SftpPath path, String view, String attrs, LinkOption... options) throws IOException {
+        throw new UnsupportedOperationException("readCustomViewAttributes(" + path + ")[" + view + ":" + attrs + "] view not supported");
+    }
+
+    protected NavigableMap<String, Object> readAclViewAttributes(SftpPath path, String view, String attrs, LinkOption... options) throws IOException {
+        if ("*".equals(attrs)) {
+            attrs = "acl,owner";
+        }
+
+        SftpFileSystem fs = path.getFileSystem();
+        SftpClient.Attributes attributes;
+        try (SftpClient client = fs.getClient()) {
+            attributes = readRemoteAttributes(path, options);
+        }
+
+        NavigableMap<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        String[] attrValues = GenericUtils.split(attrs, ',');
+        boolean traceEnabled = log.isTraceEnabled();
+        for (String attr : attrValues) {
+            switch (attr) {
+                case "acl":
+                    List<AclEntry> acl = attributes.getAcl();
+                    if (acl != null) {
+                        map.put(attr, acl);
+                    }
+                    break;
+                case "owner":
+                    String owner = attributes.getOwner();
+                    if (GenericUtils.length(owner) > 0) {
+                        map.put(attr, new SftpFileSystem.DefaultUserPrincipal(owner));
+                    }
+                    break;
+                default:
+                    if (traceEnabled) {
+                        log.trace("readAclViewAttributes({})[{}] unknown attribute: {}", fs, attrs, attr);
+                    }
+            }
+        }
+
+        return map;
+    }
+
+    public SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... options) throws IOException {
+        SftpFileSystem fs = path.getFileSystem();
+        try (SftpClient client = fs.getClient()) {
+            try {
+                SftpClient.Attributes attrs;
+                if (IoUtils.followLinks(options)) {
+                    attrs = client.stat(path.toString());
+                } else {
+                    attrs = client.lstat(path.toString());
+                }
+                if (log.isTraceEnabled()) {
+                    log.trace("readRemoteAttributes({})[{}]: {}", fs, path, attrs);
+                }
+                return attrs;
+            } catch (SftpException e) {
+                if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
+                    throw new NoSuchFileException(path.toString());
+                }
+                throw e;
+            }
+        }
+    }
+
+    protected NavigableMap<String, Object> readPosixViewAttributes(
+            SftpPath path, String view, String attrs, LinkOption... options)
+                throws IOException {
+        PosixFileAttributes v = readAttributes(path, PosixFileAttributes.class, options);
+        if ("*".equals(attrs)) {
+            attrs = "lastModifiedTime,lastAccessTime,creationTime,size,isRegularFile,isDirectory,isSymbolicLink,isOther,fileKey,owner,permissions,group";
+        }
+
+        NavigableMap<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        boolean traceEnabled = log.isTraceEnabled();
+        String[] attrValues = GenericUtils.split(attrs, ',');
+        for (String attr : attrValues) {
+            switch (attr) {
+                case "lastModifiedTime":
+                    map.put(attr, v.lastModifiedTime());
+                    break;
+                case "lastAccessTime":
+                    map.put(attr, v.lastAccessTime());
+                    break;
+                case "creationTime":
+                    map.put(attr, v.creationTime());
+                    break;
+                case "size":
+                    map.put(attr, v.size());
+                    break;
+                case "isRegularFile":
+                    map.put(attr, v.isRegularFile());
+                    break;
+                case "isDirectory":
+                    map.put(attr, v.isDirectory());
+                    break;
+                case "isSymbolicLink":
+                    map.put(attr, v.isSymbolicLink());
+                    break;
+                case "isOther":
+                    map.put(attr, v.isOther());
+                    break;
+                case "fileKey":
+                    map.put(attr, v.fileKey());
+                    break;
+                case "owner":
+                    map.put(attr, v.owner());
+                    break;
+                case "permissions":
+                    map.put(attr, v.permissions());
+                    break;
+                case "group":
+                    map.put(attr, v.group());
+                    break;
+                default:
+                    if (traceEnabled) {
+                        log.trace("readPosixViewAttributes({})[{}:{}] ignored for {}", path, view, attr, attrs);
+                    }
+            }
+        }
+        return map;
+    }
+
+    @Override
+    public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+        String view;
+        String attr;
+        int i = attribute.indexOf(':');
+        if (i == -1) {
+            view = "basic";
+            attr = attribute;
+        } else {
+            view = attribute.substring(0, i++);
+            attr = attribute.substring(i);
+        }
+
+        setAttribute(path, view, attr, value, options);
+    }
+
+    public void setAttribute(Path path, String view, String attr, Object value, LinkOption... options) throws IOException {
+        SftpPath p = toSftpPath(path);
+        SftpFileSystem fs = p.getFileSystem();
+        Collection<String> views = fs.supportedFileAttributeViews();
+        if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
+            throw new UnsupportedOperationException("setAttribute(" + path + ")[" + view + ":" + attr + "=" + value + "] view " + view + " not supported: " + views);
+        }
+
+        SftpClient.Attributes attributes = new SftpClient.Attributes();
+        switch (attr) {
+            case "lastModifiedTime":
+                attributes.modifyTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+                break;
+            case "lastAccessTime":
+                attributes.accessTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+                break;
+            case "creationTime":
+                attributes.createTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+                break;
+            case "size":
+                attributes.size(((Number) value).longValue());
+                break;
+            case "permissions": {
+                @SuppressWarnings("unchecked")
+                Set<PosixFilePermission> attrSet = (Set<PosixFilePermission>) value;
+                attributes.perms(attributesToPermissions(path, attrSet));
+                break;
+            }
+            case "owner":
+                attributes.owner(((UserPrincipal) value).getName());
+                break;
+            case "group":
+                attributes.group(((GroupPrincipal) value).getName());
+                break;
+            case "acl": {
+                ValidateUtils.checkTrue("acl".equalsIgnoreCase(view), "ACL cannot be set via view=%s", view);
+                @SuppressWarnings("unchecked")
+                List<AclEntry> acl = (List<AclEntry>) value;
+                attributes.acl(acl);
+                break;
+            }
+            case "isRegularFile":
+            case "isDirectory":
+            case "isSymbolicLink":
+            case "isOther":
+            case "fileKey":
+                throw new UnsupportedOperationException("setAttribute(" + path + ")[" + view + ":" + attr + "=" + value + "] modification N/A");
+            default:
+                if (log.isTraceEnabled()) {
+                    log.trace("setAttribute({})[{}] ignore {}:{}={}", fs, path, view, attr, value);
+                }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("setAttribute({}) {}: {}", fs, path, attributes);
+        }
+
+        try (SftpClient client = fs.getClient()) {
+            client.setStat(p.toString(), attributes);
+        }
+    }
+
+    public SftpPath toSftpPath(Path path) {
+        Objects.requireNonNull(path, "No path provided");
+        if (!(path instanceof SftpPath)) {
+            throw new ProviderMismatchException("Path is not SFTP: " + path);
+        }
+        return (SftpPath) path;
+    }
+
+    protected int attributesToPermissions(Path path, Collection<PosixFilePermission> perms) {
+        if (GenericUtils.isEmpty(perms)) {
+            return 0;
+        }
+
+        int pf = 0;
+        boolean traceEnabled = log.isTraceEnabled();
+        for (PosixFilePermission p : perms) {
+            switch (p) {
+                case OWNER_READ:
+                    pf |= SftpConstants.S_IRUSR;
+                    break;
+                case OWNER_WRITE:
+                    pf |= SftpConstants.S_IWUSR;
+                    break;
+                case OWNER_EXECUTE:
+                    pf |= SftpConstants.S_IXUSR;
+                    break;
+                case GROUP_READ:
+                    pf |= SftpConstants.S_IRGRP;
+                    break;
+                case GROUP_WRITE:
+                    pf |= SftpConstants.S_IWGRP;
+                    break;
+                case GROUP_EXECUTE:
+                    pf |= SftpConstants.S_IXGRP;
+                    break;
+                case OTHERS_READ:
+                    pf |= SftpConstants.S_IROTH;
+                    break;
+                case OTHERS_WRITE:
+                    pf |= SftpConstants.S_IWOTH;
+                    break;
+                case OTHERS_EXECUTE:
+                    pf |= SftpConstants.S_IXOTH;
+                    break;
+                default:
+                    if (traceEnabled) {
+                        log.trace("attributesToPermissions(" + path + ") ignored " + p);
+                    }
+            }
+        }
+
+        return pf;
+    }
+
+    public static String getRWXPermissions(int perms) {
+        StringBuilder sb = new StringBuilder(10 /* 3 * rwx + (d)irectory */);
+        if ((perms & SftpConstants.S_IFLNK) == SftpConstants.S_IFLNK) {
+            sb.append('l');
+        } else if ((perms & SftpConstants.S_IFDIR) == SftpConstants.S_IFDIR) {
+            sb.append('d');
+        } else {
+            sb.append('-');
+        }
+
+        if ((perms & SftpConstants.S_IRUSR) == SftpConstants.S_IRUSR) {
+            sb.append('r');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IWUSR) == SftpConstants.S_IWUSR) {
+            sb.append('w');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IXUSR) == SftpConstants.S_IXUSR) {
+            sb.append('x');
+        } else {
+            sb.append('-');
+        }
+
+        if ((perms & SftpConstants.S_IRGRP) == SftpConstants.S_IRGRP) {
+            sb.append('r');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IWGRP) == SftpConstants.S_IWGRP) {
+            sb.append('w');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IXGRP) == SftpConstants.S_IXGRP) {
+            sb.append('x');
+        } else {
+            sb.append('-');
+        }
+
+        if ((perms & SftpConstants.S_IROTH) == SftpConstants.S_IROTH) {
+            sb.append('r');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IWOTH) == SftpConstants.S_IWOTH) {
+            sb.append('w');
+        } else {
+            sb.append('-');
+        }
+        if ((perms & SftpConstants.S_IXOTH) == SftpConstants.S_IXOTH) {
+            sb.append('x');
+        } else {
+            sb.append('-');
+        }
+
+        return sb.toString();
+    }
+
+    public static String getOctalPermissions(int perms) {
+        Collection<PosixFilePermission> attrs = permissionsToAttributes(perms);
+        return getOctalPermissions(attrs);
+    }
+
+    public static Set<PosixFilePermission> permissionsToAttributes(int perms) {
+        Set<PosixFilePermission> p = EnumSet.noneOf(PosixFilePermission.class);
+        if ((perms & SftpConstants.S_IRUSR) == SftpConstants.S_IRUSR) {
+            p.add(PosixFilePermission.OWNER_READ);
+        }
+        if ((perms & SftpConstants.S_IWUSR) == SftpConstants.S_IWUSR) {
+            p.add(PosixFilePermission.OWNER_WRITE);
+        }
+        if ((perms & SftpConstants.S_IXUSR) == SftpConstants.S_IXUSR) {
+            p.add(PosixFilePermission.OWNER_EXECUTE);
+        }
+        if ((perms & SftpConstants.S_IRGRP) == SftpConstants.S_IRGRP) {
+            p.add(PosixFilePermission.GROUP_READ);
+        }
+        if ((perms & SftpConstants.S_IWGRP) == SftpConstants.S_IWGRP) {
+            p.add(PosixFilePermission.GROUP_WRITE);
+        }
+        if ((perms & SftpConstants.S_IXGRP) == SftpConstants.S_IXGRP) {
+            p.add(PosixFilePermission.GROUP_EXECUTE);
+        }
+        if ((perms & SftpConstants.S_IROTH) == SftpConstants.S_IROTH) {
+            p.add(PosixFilePermission.OTHERS_READ);
+        }
+        if ((perms & SftpConstants.S_IWOTH) == SftpConstants.S_IWOTH) {
+            p.add(PosixFilePermission.OTHERS_WRITE);
+        }
+        if ((perms & SftpConstants.S_IXOTH) == SftpConstants.S_IXOTH) {
+            p.add(PosixFilePermission.OTHERS_EXECUTE);
+        }
+        return p;
+    }
+
+    public static String getOctalPermissions(Collection<PosixFilePermission> perms) {
+        int pf = 0;
+
+        for (PosixFilePermission p : perms) {
+            switch (p) {
+                case OWNER_READ:
+                    pf |= SftpConstants.S_IRUSR;
+                    break;
+                case OWNER_WRITE:
+                    pf |= SftpConstants.S_IWUSR;
+                    break;
+                case OWNER_EXECUTE:
+                    pf |= SftpConstants.S_IXUSR;
+                    break;
+                case GROUP_READ:
+                    pf |= SftpConstants.S_IRGRP;
+                    break;
+                case GROUP_WRITE:
+                    pf |= SftpConstants.S_IWGRP;
+                    break;
+                case GROUP_EXECUTE:
+                    pf |= SftpConstants.S_IXGRP;
+                    break;
+                case OTHERS_READ:
+                    pf |= SftpConstants.S_IROTH;
+                    break;
+                case OTHERS_WRITE:
+                    pf |= SftpConstants.S_IWOTH;
+                    break;
+                case OTHERS_EXECUTE:
+                    pf |= SftpConstants.S_IXOTH;
+                    break;
+                default:    // ignored
+            }
+        }
+
+        return String.format("%04o", pf);
+    }
+
+    /**
+     * Uses the host, port and username to create a unique identifier
+     *
+     * @param uri The {@link URI} - <B>Note:</B> not checked to make sure
+     *            that the scheme is {@code sftp://}
+     * @return The unique identifier
+     * @see #getFileSystemIdentifier(String, int, String)
+     */
+    public static String getFileSystemIdentifier(URI uri) {
+        String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided");
+        String[] ui = GenericUtils.split(userInfo, ':');
+        ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
+        return getFileSystemIdentifier(uri.getHost(), uri.getPort(), ui[0]);
+    }
+
+    /**
+     * Uses the remote host address, port and current username to create a unique identifier
+     *
+     * @param session The {@link ClientSession}
+     * @return The unique identifier
+     * @see #getFileSystemIdentifier(String, int, String)
+     */
+    public static String getFileSystemIdentifier(ClientSession session) {
+        IoSession ioSession = session.getIoSession();
+        SocketAddress addr = ioSession.getRemoteAddress();
+        String username = session.getUsername();
+        if (addr instanceof InetSocketAddress) {
+            InetSocketAddress inetAddr = (InetSocketAddress) addr;
+            return getFileSystemIdentifier(inetAddr.getHostString(), inetAddr.getPort(), username);
+        } else {
+            return getFileSystemIdentifier(addr.toString(), SshConstants.DEFAULT_PORT, username);
+        }
+    }
+
+    public static String getFileSystemIdentifier(String host, int port, String username) {
+        return GenericUtils.trimToEmpty(host) + ':'
+                + ((port <= 0) ? SshConstants.DEFAULT_PORT : port) + ':'
+                + GenericUtils.trimToEmpty(username);
+    }
+
+    public static URI createFileSystemURI(String host, int port, String username, String password) {
+        return createFileSystemURI(host, port, username, password, Collections.emptyMap());
+    }
+
+    public static URI createFileSystemURI(String host, int port, String username, String password, Map<String, ?> params) {
+        ValidateUtils.checkNotNullAndNotEmpty(host, "No host provided");
+
+        String queryPart = null;
+        int numParams = GenericUtils.size(params);
+        if (numParams > 0) {
+            StringBuilder sb = new StringBuilder(numParams * Short.SIZE);
+            for (Map.Entry<String, ?> pe : params.entrySet()) {
+                String key = pe.getKey();
+                Object value = pe.getValue();
+                if (sb.length() > 0) {
+                    sb.append('&');
+                }
+                sb.append(key);
+                if (value != null) {
+                    sb.append('=').append(Objects.toString(value, null));
+                }
+            }
+
+            queryPart = sb.toString();
+        }
+
+        try {
+            String userAuth = encodeCredentials(username, password);
+            return new URI(SftpConstants.SFTP_SUBSYSTEM_NAME, userAuth, host, port, "/", queryPart, null);
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ")"
+                    + " to create access URI: " + e.getMessage(), e);
+        }
+    }
+
+    public static String encodeCredentials(String username, String password) {
+        ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided");
+        ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided");
+        /*
+         * There is no way to properly encode/decode credentials that already contain
+         * colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1:
+         *
+         *
+         *      Use of the format "user:password" in the userinfo field is
+         *      deprecated.  Applications should not render as clear text any data
+         *      after the first colon (":") character found within a userinfo
+         *      subcomponent unless the data after the colon is the empty string
+         *      (indicating no password).  Applications may choose to ignore or
+         *      reject such data when it is received as part of a reference and
+         *      should reject the storage of such data in unencrypted form.
+         */
+        ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials");
+        return username + ":" + password;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPath.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPath.java
new file mode 100644
index 0000000..381e272
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPath.java
@@ -0,0 +1,43 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.List;
+
+import org.apache.sshd.common.file.util.BasePath;
+
+public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
+    public SftpPath(SftpFileSystem fileSystem, String root, List<String> names) {
+        super(fileSystem, root, names);
+    }
+
+    @Override
+    public SftpPath toRealPath(LinkOption... options) throws IOException {
+        // TODO: handle links
+        SftpPath absolute = toAbsolutePath();
+        FileSystem fs = getFileSystem();
+        FileSystemProvider provider = fs.provider();
+        provider.checkAccess(absolute);
+        return absolute;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPathIterator.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPathIterator.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPathIterator.java
new file mode 100644
index 0000000..8c4f89e
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPathIterator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPathIterator implements Iterator<Path> {
+    private final SftpPath p;
+    private final Iterator<? extends SftpClient.DirEntry> it;
+    private boolean dotIgnored;
+    private boolean dotdotIgnored;
+    private SftpClient.DirEntry curEntry;
+
+    public SftpPathIterator(SftpPath path, Iterable<? extends SftpClient.DirEntry> iter) {
+        this(path, (iter == null) ? null : iter.iterator());
+    }
+
+    public SftpPathIterator(SftpPath path, Iterator<? extends SftpClient.DirEntry> iter) {
+        p = path;
+        it = iter;
+        curEntry = nextEntry();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return curEntry != null;
+    }
+
+    @Override
+    public Path next() {
+        if (curEntry == null) {
+            throw new NoSuchElementException("No next entry");
+        }
+
+        SftpClient.DirEntry entry = curEntry;
+        curEntry = nextEntry();
+        return p.resolve(entry.getFilename());
+    }
+
+    private SftpClient.DirEntry nextEntry() {
+        while ((it != null) && it.hasNext()) {
+            SftpClient.DirEntry entry = it.next();
+            String name = entry.getFilename();
+            if (".".equals(name) && (!dotIgnored)) {
+                dotIgnored = true;
+            } else if ("..".equals(name) && (!dotdotIgnored)) {
+                dotdotIgnored = true;
+            } else {
+                return entry;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributeView.java
new file mode 100644
index 0000000..276c4aa
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributeView.java
@@ -0,0 +1,95 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.Set;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPosixFileAttributeView extends AbstractSftpFileAttributeView implements PosixFileAttributeView {
+    public SftpPosixFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
+        super(provider, path, options);
+    }
+
+    @Override
+    public String name() {
+        return "posix";
+    }
+
+    @Override
+    public PosixFileAttributes readAttributes() throws IOException {
+        return new SftpPosixFileAttributes(path, readRemoteAttributes());
+    }
+
+    @Override
+    public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
+        SftpClient.Attributes attrs = new SftpClient.Attributes();
+        if (lastModifiedTime != null) {
+            attrs.modifyTime(lastModifiedTime);
+        }
+        if (lastAccessTime != null) {
+            attrs.accessTime(lastAccessTime);
+        }
+        if (createTime != null) {
+            attrs.createTime(createTime);
+        }
+
+        if (GenericUtils.isEmpty(attrs.getFlags())) {
+            if (log.isDebugEnabled()) {
+                log.debug("setTimes({}) no changes", path);
+            }
+        } else {
+            writeRemoteAttributes(attrs);
+        }
+    }
+
+    @Override
+    public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
+        provider.setAttribute(path, "permissions", perms, options);
+    }
+
+    @Override
+    public void setGroup(GroupPrincipal group) throws IOException {
+        provider.setAttribute(path, "group", group, options);
+    }
+
+    @Override
+    public UserPrincipal getOwner() throws IOException {
+        return readAttributes().owner();
+    }
+
+    @Override
+    public void setOwner(UserPrincipal owner) throws IOException {
+        provider.setAttribute(path, "owner", owner, options);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributes.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributes.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributes.java
new file mode 100644
index 0000000..0c6c5fe
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributes.java
@@ -0,0 +1,113 @@
+/*
+ * 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.client.subsystem.sftp.fs;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.Set;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPosixFileAttributes implements PosixFileAttributes {
+    private final Path path;
+    private final Attributes attributes;
+
+    public SftpPosixFileAttributes(Path path, Attributes attributes) {
+        this.path = path;
+        this.attributes = attributes;
+    }
+
+    /**
+     * @return The referenced attributes file {@link Path}
+     */
+    public final Path getPath() {
+        return path;
+    }
+
+    @Override
+    public UserPrincipal owner() {
+        String owner = attributes.getOwner();
+        return GenericUtils.isEmpty(owner) ? null : new SftpFileSystem.DefaultUserPrincipal(owner);
+    }
+
+    @Override
+    public GroupPrincipal group() {
+        String group = attributes.getGroup();
+        return GenericUtils.isEmpty(group) ? null : new SftpFileSystem.DefaultGroupPrincipal(group);
+    }
+
+    @Override
+    public Set<PosixFilePermission> permissions() {
+        return SftpFileSystemProvider.permissionsToAttributes(attributes.getPermissions());
+    }
+
+    @Override
+    public FileTime lastModifiedTime() {
+        return attributes.getModifyTime();
+    }
+
+    @Override
+    public FileTime lastAccessTime() {
+        return attributes.getAccessTime();
+    }
+
+    @Override
+    public FileTime creationTime() {
+        return attributes.getCreateTime();
+    }
+
+    @Override
+    public boolean isRegularFile() {
+        return attributes.isRegularFile();
+    }
+
+    @Override
+    public boolean isDirectory() {
+        return attributes.isDirectory();
+    }
+
+    @Override
+    public boolean isSymbolicLink() {
+        return attributes.isSymbolicLink();
+    }
+
+    @Override
+    public boolean isOther() {
+        return attributes.isOther();
+    }
+
+    @Override
+    public long size() {
+        return attributes.getSize();
+    }
+
+    @Override
+    public Object fileKey() {
+        // TODO consider implementing this
+        return null;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
index 0fce423..8892e72 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
@@ -27,9 +27,9 @@ import java.nio.file.attribute.FileAttributeView;
 import java.util.Objects;
 
 import org.apache.sshd.client.subsystem.sftp.SftpClient;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
-import org.apache.sshd.client.subsystem.sftp.SftpPath;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpPath;
 import org.apache.sshd.common.subsystem.sftp.SftpConstants;
 import org.apache.sshd.common.subsystem.sftp.SftpException;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
index c6702f8..ec65752 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
@@ -25,9 +25,9 @@ import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.subsystem.sftp.SftpClient;
 import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
 import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
 /**