Skip to content

Commit

Permalink
SFTPFileSystemProvider.getPath now tries to create a new file system …
Browse files Browse the repository at this point in the history
…if needed
  • Loading branch information
robtimus committed Dec 2, 2023
1 parent 6593389 commit 9ffdfae
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 65 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
</issueManagement>

<properties>
<version.fs-core>2.2</version.fs-core>
<version.fs-core>2.3</version.fs-core>
<version.jsch>0.2.8</version.jsch>
<version.junit-support>2.2</version.junit-support>
<version.simple-pool>1.0</version.simple-pool>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BinaryOperator;
import com.github.robtimus.filesystems.FileSystemProviderSupport;
import com.github.robtimus.filesystems.Messages;
Expand All @@ -58,6 +59,8 @@
*/
public class SFTPEnvironment implements Map<String, Object> {

private static final AtomicReference<SFTPEnvironment> DEFAULTS = new AtomicReference<>();

// session support

private static final String USERNAME = "username"; //$NON-NLS-1$
Expand Down Expand Up @@ -916,7 +919,25 @@ public String toString() {
* @since 3.0
*/
public static SFTPEnvironment copy(Map<String, ?> env) {
return new SFTPEnvironment(new HashMap<>(env));
return env == null
? new SFTPEnvironment()
: new SFTPEnvironment(new HashMap<>(env));
}

/**
* Sets the default SFTP environment.
* This is used in {@link SFTPFileSystemProvider#getPath(URI)} when a file system needs to be created, since no environment can be passed.
* This way, certain settings like {@link #withPoolConfig(SFTPPoolConfig) pool configuration} can still be applied.
*
* @param defaultEnvironment The default SFTP environment. Use {@code null} to reset it to an empty environment.
* @since 3.3
*/
public static void setDefault(SFTPEnvironment defaultEnvironment) {
DEFAULTS.set(copy(defaultEnvironment));
}

static SFTPEnvironment copyOfDefault() {
return copy(DEFAULTS.get());
}

static final class AppendedConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,7 @@ void keepAlive() throws IOException {
}

URI toUri(SFTPPath path) {
SFTPPath absPath = toAbsolutePath(path).normalize();
return toUri(absPath.path());
return toUri(toAbsolutePath(path).normalizedPath());
}

URI toUri(String path) {
Expand Down Expand Up @@ -247,7 +246,7 @@ private SFTPPathAndAttributesPair(SFTPPath path, SftpATTRS attributes) {
}

private String normalizePath(SFTPPath path) {
return path.toAbsolutePath().normalize().path();
return path.toAbsolutePath().normalizedPath();
}

String toString(SFTPPath path) {
Expand Down Expand Up @@ -543,7 +542,7 @@ void move(SFTPPath source, SFTPPath target, CopyOption... options) throws IOExce
getAttributes(channel, normalizedSource, false);
}

if (toAbsolutePath(source).parentPath() == null) {
if (ROOT_PATH.equals(normalizedSource)) {
// cannot move or rename the root
throw new DirectoryNotEmptyException(source.path());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ private SFTPFileSystem createFileSystem(URI uri, Map<String, ?> env) throws IOEx
*/
@Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
SFTPEnvironment environment = env == null
? new SFTPEnvironment()
: SFTPEnvironment.copy(env);
SFTPEnvironment environment = SFTPEnvironment.copy(env);

boolean allowUserInfo = !environment.hasUsername();
boolean allowPath = !environment.hasDefaultDir();
Expand Down Expand Up @@ -137,29 +135,45 @@ private void addDefaultDirIfNeeded(SFTPEnvironment environment, String path) {
public FileSystem getFileSystem(URI uri) {
checkURI(uri, true, false);

return getExistingFileSystem(uri);
URI normalizedURI = normalizeWithoutPassword(uri);
return fileSystems.get(normalizedURI);
}

/**
* Return a {@code Path} object by converting the given {@link URI}. The resulting {@code Path} is associated with a {@link FileSystem} that
* already exists. This method does not support constructing {@code FileSystem}s automatically.
* already exists or is constructed automatically.
* <p>
* The URI must have a {@link URI#getScheme() scheme} equal to {@link #getScheme()}, and no {@link URI#getQuery() query} or
* {@link URI#getFragment() fragment}. Because the original credentials were possibly provided through an environment map,
* the URI can contain {@link URI#getUserInfo() user information}, although this should not contain a password for security reasons.
* the URI can contain {@link URI#getUserInfo() user information}, although for security reasons this should only contain a password to support
* automatically creating file systems.
* <p>
* If no matching file system existed yet, a new one is created. The {@link SFTPEnvironment#setDefault(SFTPEnvironment) default environment} is
* used for this, to allow configuring the resulting file system.
* <p>
* Remember to close any newly created file system.
*/
@Override
@SuppressWarnings("resource")
public Path getPath(URI uri) {
checkURI(uri, true, true);

SFTPFileSystem fs = getExistingFileSystem(uri);
return fs.getPath(uri.getPath());
}

private SFTPFileSystem getExistingFileSystem(URI uri) {
URI normalizedURI = normalizeWithoutPassword(uri);
return fileSystems.get(normalizedURI);
SFTPEnvironment env = SFTPEnvironment.copyOfDefault();

addUserInfoIfNeeded(env, uri.getUserInfo());
// Do not add any default dir

try {
SFTPFileSystem fs = fileSystems.addIfNotExists(normalizedURI, env);
return fs.getPath(uri.getPath());
} catch (IOException e) {
// FileSystemProvider.getPath mandates that a FileSystemNotFoundException should be thrown if no file system could be created
// automatically
FileSystemNotFoundException exception = new FileSystemNotFoundException(normalizedURI.toString());
exception.initCause(e);
throw exception;
}
}

private void checkURI(URI uri, boolean allowUserInfo, boolean allowPath) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
Expand Down Expand Up @@ -61,7 +62,7 @@
class SFTPFileSystemProviderTest extends AbstractSFTPFileSystemTest {

@Nested
class PathAndFiles {
class PathsAndFiles {

@Test
void testSuccess() throws IOException {
Expand Down Expand Up @@ -101,7 +102,7 @@ void testSuccess() throws IOException {

@Test
void testFileSystemNotFound() {
URI uri = URI.create("sftp://sftp.github.com/");
URI uri = getURI();
FileSystemNotFoundException exception = assertThrows(FileSystemNotFoundException.class, () -> Paths.get(uri));
assertEquals(normalizeWithUsername(uri, null).toString(), exception.getMessage());
assertEquals(normalizeWithoutPassword(uri).toString(), exception.getMessage());
Expand All @@ -113,7 +114,7 @@ class NewFileSystem {

@Test
void testWithMinimalEnv() throws IOException {
URI uri = URI.create(getBaseUrlWithCredentials() + "/" + getDefaultDir());
URI uri = URI.create(getBaseUrlWithCredentials() + getDefaultDir());
try (FileSystem fs = FileSystems.newFileSystem(uri, createMinimalEnv())) {
Path path = fs.getPath("");
assertEquals(getDefaultDir(), path.toAbsolutePath().toString());
Expand All @@ -122,7 +123,7 @@ void testWithMinimalEnv() throws IOException {

@Test
void testWithMinimalIdentityEnv() throws IOException {
URI uri = URI.create(getBaseUrl() + "/" + getDefaultDir());
URI uri = URI.create(getBaseUrl() + getDefaultDir());
try (FileSystem fs = FileSystems.newFileSystem(uri, createMinimalIdentityEnv())) {
Path path = fs.getPath("");
assertEquals(getDefaultDir(), path.toAbsolutePath().toString());
Expand All @@ -134,31 +135,31 @@ void testWithUserInfoAndCredentials() {
URI uri = URI.create(getBaseUrl());
SFTPEnvironment env = createEnv();
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> FileSystems.newFileSystem(uri, env));
assertEquals(Messages.uri().hasUserInfo(uri).getMessage(), exception.getMessage());
assertChainEquals(Messages.uri().hasUserInfo(uri), exception);
}

@Test
void testWithPathAndDefaultDir() {
URI uri = getURI().resolve("/path");
SFTPEnvironment env = createEnv();
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> FileSystems.newFileSystem(uri, env));
assertEquals(Messages.uri().hasPath(uri).getMessage(), exception.getMessage());
assertChainEquals(Messages.uri().hasPath(uri), exception);
}

@Test
void testWithQuery() {
URI uri = getURI().resolve("?q=v");
SFTPEnvironment env = createEnv();
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> FileSystems.newFileSystem(uri, env));
assertEquals(Messages.uri().hasQuery(uri).getMessage(), exception.getMessage());
assertChainEquals(Messages.uri().hasQuery(uri), exception);
}

@Test
void testWithFragment() {
URI uri = getURI().resolve("#id");
SFTPEnvironment env = createEnv();
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> FileSystems.newFileSystem(uri, env));
assertEquals(Messages.uri().hasFragment(uri).getMessage(), exception.getMessage());
assertChainEquals(Messages.uri().hasFragment(uri), exception);
}
}

Expand Down Expand Up @@ -189,6 +190,13 @@ void testExistingWithTrailingSlash() throws IOException {
}
}

@Test
void testWithNonEmptyPath() {
URI uri = URI.create(getBaseUrl() + "/foo");
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> FileSystems.getFileSystem(uri));
assertChainEquals(Messages.uri().hasPath(uri), exception);
}

@Test
void testNotExisting() {
URI uri = URI.create(getBaseUrl());
Expand Down Expand Up @@ -275,7 +283,7 @@ private void testNormalizeWithoutPassword(String uriAddition) {
class NormalizeWithUsername {

@Test
void testMinimalURIWithout() {
void testMinimalURIWithoutUserInfo() {
URI uri = getURI();
assertSame(uri, SFTPFileSystemProvider.normalizeWithUsername(uri, null));
}
Expand Down Expand Up @@ -355,11 +363,60 @@ void testInvalidScheme() {
}

@Test
void testFileSystemNotFound() {
void testFileSystemCreatedWithPath() {
SFTPFileSystemProvider provider = new SFTPFileSystemProvider();
URI uri = URI.create(getBaseUrlWithCredentials() + "/foo");
SFTPEnvironment.setDefault(createMinimalEnv());
try {
Path path = assertDoesNotThrow(() -> provider.getPath(uri));
assertNotEquals(uri, path.toUri());
assertEquals("/foo", path.toAbsolutePath().toString());
assertFalse(Files.exists(path));
assertDoesNotThrow(() -> path.getFileSystem().close());
} finally {
SFTPEnvironment.setDefault(null);
}
}

@Test
void testFileSystemCreatedWithoutPath() {
SFTPFileSystemProvider provider = new SFTPFileSystemProvider();
URI uri = URI.create(getBaseUrlWithCredentials());
SFTPEnvironment.setDefault(createMinimalEnv()
.withDefaultDirectory(getDefaultDir()));
try {
Path path = assertDoesNotThrow(() -> provider.getPath(uri));
assertNotEquals(uri, path.toUri());
assertEquals(getDefaultDir(), path.toAbsolutePath().toString());
assertTrue(Files.exists(path));
assertDoesNotThrow(() -> path.getFileSystem().close());
} finally {
SFTPEnvironment.setDefault(null);
}
}

@Test
void testFileSystemCreatedWithIdentity() {
SFTPFileSystemProvider provider = new SFTPFileSystemProvider();
URI uri = URI.create("sftp://sftp.github.com/");
URI uri = URI.create(getBaseUrl() + "/foo");
SFTPEnvironment.setDefault(createMinimalIdentityEnv());
try {
Path path = assertDoesNotThrow(() -> provider.getPath(uri));
assertEquals(uri, path.toUri());
assertEquals("/foo", path.toAbsolutePath().toString());
assertFalse(Files.exists(path));
assertDoesNotThrow(() -> path.getFileSystem().close());
} finally {
SFTPEnvironment.setDefault(null);
}
}

@Test
void testFileSystemCreationFailure() {
SFTPFileSystemProvider provider = new SFTPFileSystemProvider();
URI uri = URI.create(getBaseUrlWithCredentials());
// Cause: unknown host key
FileSystemNotFoundException exception = assertThrows(FileSystemNotFoundException.class, () -> provider.getPath(uri));
assertEquals(normalizeWithUsername(uri, null).toString(), exception.getMessage());
assertEquals(normalizeWithoutPassword(uri).toString(), exception.getMessage());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1730,7 +1730,7 @@ void testMoveCurrentDir(String dir) throws IOException {
@ParameterizedTest
@ValueSource(strings = CURRENT_DIR)
@EmptySource
void testCopyToCurrentDir(String dir) throws IOException {
void testMoveToCurrentDir(String dir) throws IOException {
addDirectory("/baz");
addFile("/baz/qux");

Expand Down Expand Up @@ -2058,7 +2058,7 @@ void testFileNoFollowLinks() throws IOException {
}

@Test
void testDirectoryFollowLinksForDirectory() throws IOException {
void testDirectoryFollowLinks() throws IOException {
addDirectory("/foo");

PosixFileAttributes attributes = provider().readAttributes(createPath("/foo"), PosixFileAttributes.class);
Expand All @@ -2075,23 +2075,7 @@ void testDirectoryFollowLinksForDirectory() throws IOException {
}

@Test
void testDirectoryFollowLinksForFile() throws IOException {
Path foo = addFile("/foo");

PosixFileAttributes attributes = provider().readAttributes(createPath("/foo"), PosixFileAttributes.class);

assertEquals(Files.size(foo), attributes.size());
assertNotNull(attributes.owner().getName());
assertNotNull(attributes.group().getName());
assertNotNull(attributes.permissions());
assertFalse(attributes.isDirectory());
assertTrue(attributes.isRegularFile());
assertFalse(attributes.isSymbolicLink());
assertFalse(attributes.isOther());
}

@Test
void testDirectoryNoFollowLinksForDirectory() throws IOException {
void testDirectoryNoFollowLinks() throws IOException {
addDirectory("/foo");

PosixFileAttributes attributes = provider().readAttributes(createPath("/foo"), PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
Expand All @@ -2107,22 +2091,6 @@ void testDirectoryNoFollowLinksForDirectory() throws IOException {
assertFalse(attributes.isOther());
}

@Test
void testDirectoryNoFollowLinksForFile() throws IOException {
Path foo = addFile("/foo");

PosixFileAttributes attributes = provider().readAttributes(createPath("/foo"), PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS);

assertEquals(Files.size(foo), attributes.size());
assertNotNull(attributes.owner().getName());
assertNotNull(attributes.group().getName());
assertNotNull(attributes.permissions());
assertFalse(attributes.isDirectory());
assertTrue(attributes.isRegularFile());
assertFalse(attributes.isSymbolicLink());
assertFalse(attributes.isOther());
}

@ParameterizedTest
@ValueSource(strings = CURRENT_DIR)
@EmptySource
Expand Down

0 comments on commit 9ffdfae

Please sign in to comment.