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:d...@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:d...@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:d...@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() + "]";
+    }
+}

Reply via email to