diff --git a/core/src/main/java/com/palantir/giraffe/SystemConverter.java b/core/src/main/java/com/palantir/giraffe/SystemConverter.java
deleted file mode 100644
index d3d39c35..00000000
--- a/core/src/main/java/com/palantir/giraffe/SystemConverter.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/**
- * Copyright 2015 Palantir Technologies, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.palantir.giraffe;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-
-import com.palantir.giraffe.command.Command;
-import com.palantir.giraffe.command.ExecutionSystem;
-import com.palantir.giraffe.command.ExecutionSystemConvertible;
-import com.palantir.giraffe.command.ExecutionSystems;
-import com.palantir.giraffe.file.FileSystemConvertible;
-
-/**
- * Creates {@link FileSystem} or {@link ExecutionSystem} instances from systems
- * of the other type.
- *
- * Converting one system to another does not affect the original system. The new
- * system is independent and closing either system does not close the other.
- * Where possible, the new system minimizes resource duplication, reusing
- * connections or other state that is safely shared.
- *
- * Not all file systems have a corresponding execution system and not all
- * execution systems have a corresponding file system. If a conversion is not
- * possible, methods in this class throw {@code UnsupportedOperationException}.
- * Conversion between the default systems of each type is always supported. See
- * provider-specific documentation for details about the compatibility of other
- * system implementations.
- *
- * @author bkeyes
- *
- * @see FileSystemConvertible
- * @see ExecutionSystemConvertible
- */
-public class SystemConverter {
-
- /**
- * Returns an open {@link FileSystem} that accesses the same resources as
- * the given command's execution system.
- *
- * @param command the command
- *
- * @throws IOException if an I/O error occurs while creating the
- * {@code FileSystem}
- *
- * @see #asFileSystem(ExecutionSystem)
- */
- public static FileSystem asFileSystem(Command command) throws IOException {
- checkNotNull(command, "command must be non-null");
- return asFileSystem(command.getExecutionSystem());
- }
-
- /**
- * Returns an open {@link FileSystem} that accesses the same resources as
- * the given execution system. The execution system is not modified.
- *
- * The file system can read and modify the same files that are accessible to
- * commands executed by the execution system. Changes made by one system are
- * visible to the other, given certain system-dependent conditions are true.
- * In particular, no guarantees are made for concurrent modification or
- * modifications from different threads.
- *
- * @param es the execution system
- *
- * @return an open {@code FileSystem}
- *
- * @throws IOException if an I/O error occurs while creating the
- * {@code FileSystem}
- * @throws UnsupportedOperationException if the given execution system does
- * not support file system conversion
- */
- public static FileSystem asFileSystem(ExecutionSystem es) throws IOException {
- checkNotNull(es, "system must be non-null");
- if (es.equals(ExecutionSystems.getDefault())) {
- return FileSystems.getDefault();
- } else if (es instanceof FileSystemConvertible) {
- return ((FileSystemConvertible) es).asFileSystem();
- } else {
- throw uncovertableError(es.getClass());
- }
- }
-
- /**
- * Returns an open {@link ExecutionSystem} that accesses the same resources
- * as the given path's file system.
- *
- * @param path the path
- *
- * @throws IOException if an I/O error occurs while creating the
- * {@code ExecutionSystem}
- *
- * @see #asExecutionSystem(FileSystem)
- */
- public static ExecutionSystem asExecutionSystem(Path path) throws IOException {
- checkNotNull(path, "path must be non-null");
- return asExecutionSystem(path.getFileSystem());
- }
-
- /**
- * Returns an open {@link ExecutionSystem} that accesses the same resources
- * as the given file system. The file system is not modified.
- *
- * Commands executed by the execution system can read and modify the same
- * files that are accessible by the file system. Changes made by one system
- * are visible to the other, given certain system-dependent conditions are
- * true. In particular, no guarantees are made for concurrent modification
- * or modifications from different threads.
- *
- * @param fs the file system
- *
- * @return an open {@code ExecutionSystem}
- *
- * @throws IOException if an I/O error occurs while creating the
- * {@code ExecutionSystem}
- * @throws UnsupportedOperationException if the given file system does not
- * support execution system conversion
- */
- public static ExecutionSystem asExecutionSystem(FileSystem fs) throws IOException {
- checkNotNull(fs, "system must be non-null");
- if (fs.equals(FileSystems.getDefault())) {
- return ExecutionSystems.getDefault();
- } else if (fs instanceof ExecutionSystemConvertible) {
- return ((ExecutionSystemConvertible) fs).asExecutionSystem();
- } else {
- throw uncovertableError(fs.getClass());
- }
- }
-
- /**
- * Returns {@code true} if the given file system can be converted into an
- * execution system.
- *
- * @param fs the file system
- */
- public static boolean isConvertible(FileSystem fs) {
- return FileSystems.getDefault().equals(fs) || fs instanceof ExecutionSystemConvertible;
- }
-
- /**
- * Returns {@code true} if the given execution system can be converted into
- * a file system.
- *
- * @param es the execution system
- */
- public static boolean isConvertible(ExecutionSystem es) {
- return ExecutionSystems.getDefault().equals(es) || es instanceof FileSystemConvertible;
- }
-
- private static UnsupportedOperationException uncovertableError(Class> systemClass) {
- String msg = systemClass.getName() + " is not convertible";
- throw new UnsupportedOperationException(msg);
- }
-
- private SystemConverter() {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/core/src/main/java/com/palantir/giraffe/SystemUpgrader.java b/core/src/main/java/com/palantir/giraffe/SystemUpgrader.java
new file mode 100644
index 00000000..98aa2763
--- /dev/null
+++ b/core/src/main/java/com/palantir/giraffe/SystemUpgrader.java
@@ -0,0 +1,151 @@
+/**
+ * Copyright 2015 Palantir Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.palantir.giraffe;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+
+import com.palantir.giraffe.command.ExecutionSystem;
+import com.palantir.giraffe.command.ExecutionSystems;
+import com.palantir.giraffe.host.HostAccessors;
+import com.palantir.giraffe.host.HostControlSystem;
+import com.palantir.giraffe.host.HostControlSystemUpgradeable;
+
+/**
+ * Creates {@link HostControlSystem} instances from {@link FileSystem} and
+ * {@link ExecutionSystem} instances.
+ *
+ * Converting one system to another does not affect the original system. The new
+ * system is an always-open view of the original system. Calling {@code close()}
+ * has no effect and closing the source system closes all views.
+ *
+ * Where possible, the new view minimizes resource duplication, reusing
+ * connections or other state that is safely shared.
+ *
+ * Not all file systems and execution systems can be upgraded to
+ * {@code HostControlSystem}s. If a conversion is not possible, methods in this
+ * class throw {@code UnsupportedOperationException}. Upgrading the default
+ * systems of either type is always supported. See provider-specific
+ * documentation for details about the compatibility of other system
+ * implementations.
+ *
+ * @author bkeyes
+ *
+ * @see HostControlSystemUpgradeable
+ */
+public class SystemUpgrader {
+
+ private static final Upgrader fsUpgrader =
+ new Upgrader<>(FileSystems.getDefault());
+
+ private static final Upgrader esUpgrader =
+ new Upgrader<>(ExecutionSystems.getDefault());
+
+ /**
+ * Returns an open {@link HostControlSystem} that accesses the same
+ * resources as the given execution system. The execution system is not
+ * modified.
+ *
+ * The new system can read and modify the same files that are accessible to
+ * commands executed by the execution system. Changes made by one system are
+ * visible to the other, given certain system-dependent conditions are true.
+ * In particular, no guarantees are made for concurrent modification or
+ * modifications from different threads.
+ *
+ * @param es the execution system
+ *
+ * @return an open {@code HostControlSystem}
+ *
+ * @throws IOException if an I/O error occurs while creating the system
+ * @throws UnsupportedOperationException if the execution system does not
+ * support upgrades
+ */
+ public static HostControlSystem upgrade(ExecutionSystem es) throws IOException {
+ return esUpgrader.upgrade(es);
+ }
+
+ /**
+ * Returns an open {@link HostControlSystem} that accesses the same
+ * resources as the given file system. The file system is not modified.
+ *
+ * Commands executed by the new system can read and modify the same files
+ * that are accessible by the file system. Changes made by one system are
+ * visible to the other, given certain system-dependent conditions are true.
+ * In particular, no guarantees are made for concurrent modification or
+ * modifications from different threads.
+ *
+ * @param fs the file system
+ *
+ * @return an open {@code HostControlSystem}
+ *
+ * @throws IOException if an I/O error occurs while creating the system
+ * @throws UnsupportedOperationException if the file system does not support
+ * upgrades
+ */
+ public static HostControlSystem upgrade(FileSystem fs) throws IOException {
+ return fsUpgrader.upgrade(fs);
+ }
+
+ /**
+ * Returns {@code true} if the given file system can be upgrade to a host
+ * control system.
+ */
+ public static boolean isUpgradeable(FileSystem fs) {
+ return fsUpgrader.isUpgradeable(fs);
+ }
+
+ /**
+ * Returns {@code true} if the given execution system can be upgraded to a
+ * host control system.
+ */
+ public static boolean isUpgradeable(ExecutionSystem es) {
+ return esUpgrader.isUpgradeable(es);
+ }
+
+ private static final class Upgrader {
+ private final S defaultSystem;
+
+ Upgrader(S defaultSystem) {
+ this.defaultSystem = defaultSystem;
+ }
+
+ public boolean isUpgradeable(S system) {
+ checkNotNull(system, "system must be non-null");
+ return defaultSystem.equals(system) || system instanceof HostControlSystemUpgradeable;
+ }
+
+ public HostControlSystem upgrade(S system) throws IOException {
+ checkNotNull(system, "system must be non-null");
+ if (!isUpgradeable(system)) {
+ String msg = system.getClass().getName() + " is not upgradeable";
+ throw new UnsupportedOperationException(msg);
+ }
+
+ if (system.equals(defaultSystem)) {
+ return HostAccessors.getDefault().open();
+ } else {
+ return ((HostControlSystemUpgradeable) system).asHostControlSystem();
+ }
+ }
+ }
+
+ private SystemUpgrader() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/src/main/java/com/palantir/giraffe/command/ExecutionSystemConvertible.java b/core/src/main/java/com/palantir/giraffe/command/ExecutionSystemConvertible.java
deleted file mode 100644
index f149989d..00000000
--- a/core/src/main/java/com/palantir/giraffe/command/ExecutionSystemConvertible.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Copyright 2015 Palantir Technologies, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.palantir.giraffe.command;
-
-import java.io.IOException;
-
-/**
- * Systems that implement this interface can be converted into
- * {@link ExecutionSystem} instances.
- *
- * @author bkeyes
- */
-public interface ExecutionSystemConvertible {
-
- /**
- * Returns an open {@link ExecutionSystem} that accesses the same resources
- * as this system.
- *
- * The returned system is independent from this system and either can be
- * closed without affecting the other.
- *
- * @throws IOException if an I/O error occurs while creating the new system
- */
- ExecutionSystem asExecutionSystem() throws IOException;
-
-}
diff --git a/core/src/main/java/com/palantir/giraffe/file/FileSystemConvertible.java b/core/src/main/java/com/palantir/giraffe/host/HostControlSystemUpgradeable.java
similarity index 63%
rename from core/src/main/java/com/palantir/giraffe/file/FileSystemConvertible.java
rename to core/src/main/java/com/palantir/giraffe/host/HostControlSystemUpgradeable.java
index d09d1ce6..e79d4901 100644
--- a/core/src/main/java/com/palantir/giraffe/file/FileSystemConvertible.java
+++ b/core/src/main/java/com/palantir/giraffe/host/HostControlSystemUpgradeable.java
@@ -13,28 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.palantir.giraffe.file;
+package com.palantir.giraffe.host;
import java.io.IOException;
-import java.nio.file.FileSystem;
/**
- * Systems that implement this interface can be converted into
- * {@link FileSystem} instances.
+ * Systems that implement this interface can be upgraded into
+ * {@link HostControlSystem} instances.
*
* @author bkeyes
*/
-public interface FileSystemConvertible {
+public interface HostControlSystemUpgradeable {
/**
- * Returns an open {@link FileSystem} that accesses the same resources as
+ * Returns a {@link HostControlSystem} view of the resources accessible with
* this system.
*
- * The returned system is independent from this system and either can be
- * closed without affecting the other.
+ * The returned view is always open and calling {@code close()} has no
+ * effect. Closing the source system closes all views.
*
* @throws IOException if an I/O error occurs while creating the new system
*/
- FileSystem asFileSystem() throws IOException;
+ HostControlSystem asHostControlSystem() throws IOException;
}
diff --git a/core/src/test/java/com/palantir/giraffe/command/SystemConverterTest.java b/core/src/test/java/com/palantir/giraffe/command/SystemConverterTest.java
deleted file mode 100644
index d4ea5b74..00000000
--- a/core/src/test/java/com/palantir/giraffe/command/SystemConverterTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/**
- * Copyright 2015 Palantir Technologies, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.palantir.giraffe.command;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.palantir.giraffe.SystemConverter;
-import com.palantir.giraffe.file.FileSystemConvertible;
-
-/**
- * Tests basic functionality of {@link SystemConverter} methods.
- *
- * @author bkeyes
- */
-public class SystemConverterTest {
-
- private abstract static class ConvertibleFileSystem extends FileSystem
- implements ExecutionSystemConvertible {
- // implementation is mocked
- }
-
- private abstract static class ConvertibleExecutionSystem extends ExecutionSystem
- implements FileSystemConvertible {
- // implementation is mocked
- }
-
- private ConvertibleFileSystem fs;
- private Path path;
-
- private ConvertibleExecutionSystem es;
- private Command command;
-
- @Before
- public void setup() throws IOException {
- fs = mock(ConvertibleFileSystem.class);
- es = mock(ConvertibleExecutionSystem.class);
-
- path = mock(Path.class);
- command = mock(Command.class);
-
- when(fs.asExecutionSystem()).thenReturn(es);
- when(es.asFileSystem()).thenReturn(fs);
-
- when(path.getFileSystem()).thenReturn(fs);
- when(command.getExecutionSystem()).thenReturn(es);
- }
-
- // --- file system conversion ---
-
- @Test
- public void convertFileSystem() throws IOException {
- assertEquals("conversion is incorrect", es, SystemConverter.asExecutionSystem(fs));
- }
-
- @Test
- public void convertPath() throws IOException {
- assertEquals("conversion is incorrect", es, SystemConverter.asExecutionSystem(path));
- }
-
- @Test
- public void convertLocalFileSystem() throws IOException {
- ExecutionSystem converted = SystemConverter.asExecutionSystem(FileSystems.getDefault());
- assertEquals("conversion is incorrect", ExecutionSystems.getDefault(), converted);
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void uncovertableFileSystem() throws IOException {
- SystemConverter.asExecutionSystem(mock(FileSystem.class));
- }
-
- @Test
- public void fileSystemIsConvertible() {
- assertTrue("system is not convertible", SystemConverter.isConvertible(fs));
-
- FileSystem defaultFs = FileSystems.getDefault();
- assertTrue("system is not convertible", SystemConverter.isConvertible(defaultFs));
- }
-
- // --- execution system conversion ---
-
- @Test
- public void convertExecutionSystem() throws IOException {
- assertEquals("conversion is incorrect", fs, SystemConverter.asFileSystem(es));
- }
-
- @Test
- public void convertCommand() throws IOException {
- assertEquals("conversion is incorrect", fs, SystemConverter.asFileSystem(command));
- }
-
- @Test
- public void convertLocalExecutionSystem() throws IOException {
- FileSystem converted = SystemConverter.asFileSystem(ExecutionSystems.getDefault());
- assertEquals("conversion is incorrect", FileSystems.getDefault(), converted);
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void uncovertableExecutionSystem() throws IOException {
- SystemConverter.asFileSystem(mock(ExecutionSystem.class));
- }
-
- @Test
- public void executionSystemIsConvertible() {
- assertTrue("system is not convertible", SystemConverter.isConvertible(es));
-
- ExecutionSystem defaultEs = ExecutionSystems.getDefault();
- assertTrue("system is not convertible", SystemConverter.isConvertible(defaultEs));
- }
-}
diff --git a/core/src/test/java/com/palantir/giraffe/command/SystemUpgraderTest.java b/core/src/test/java/com/palantir/giraffe/command/SystemUpgraderTest.java
new file mode 100644
index 00000000..f49914aa
--- /dev/null
+++ b/core/src/test/java/com/palantir/giraffe/command/SystemUpgraderTest.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright 2015 Palantir Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.palantir.giraffe.command;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.palantir.giraffe.SystemUpgrader;
+import com.palantir.giraffe.host.HostAccessors;
+import com.palantir.giraffe.host.HostControlSystem;
+import com.palantir.giraffe.host.HostControlSystemUpgradeable;
+
+/**
+ * Tests basic functionality of {@link SystemUpgrader} methods.
+ *
+ * @author bkeyes
+ */
+public class SystemUpgraderTest {
+
+ private abstract static class ConvertibleFileSystem extends FileSystem
+ implements HostControlSystemUpgradeable {
+ // implementation is mocked
+ }
+
+ private abstract static class ConvertibleExecutionSystem extends ExecutionSystem
+ implements HostControlSystemUpgradeable {
+ // implementation is mocked
+ }
+
+ private ConvertibleFileSystem fs;
+ private ConvertibleExecutionSystem es;
+ private HostControlSystem hcs;
+
+ @Before
+ public void setup() throws IOException {
+ fs = mock(ConvertibleFileSystem.class);
+ es = mock(ConvertibleExecutionSystem.class);
+ hcs = mock(HostControlSystem.class);
+
+ when(fs.asHostControlSystem()).thenReturn(hcs);
+ when(es.asHostControlSystem()).thenReturn(hcs);
+ }
+
+ @Test
+ public void upgradeFileSystem() throws IOException {
+ assertEquals("upgrade is incorrect", hcs, SystemUpgrader.upgrade(fs));
+ }
+
+ @Test
+ public void upgradeLocalFileSystem() throws IOException {
+ HostControlSystem upgraded = SystemUpgrader.upgrade(FileSystems.getDefault());
+ assertEquals("upgrade is incorrect", HostAccessors.getDefault().open(), upgraded);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void unsupportedFileSystem() throws IOException {
+ SystemUpgrader.upgrade(mock(FileSystem.class));
+ }
+
+ @Test
+ public void fileSystemIsUpgradeable() {
+ assertTrue("system is not upgradeable", SystemUpgrader.isUpgradeable(fs));
+
+ FileSystem defaultFs = FileSystems.getDefault();
+ assertTrue("system is not upgradaeble", SystemUpgrader.isUpgradeable(defaultFs));
+ }
+
+ @Test
+ public void upgradeExecutionSystem() throws IOException {
+ assertEquals("upgrade is incorrect", hcs, SystemUpgrader.upgrade(es));
+ }
+
+ @Test
+ public void upgradeLocalExecutionSystem() throws IOException {
+ HostControlSystem upgraded = SystemUpgrader.upgrade(ExecutionSystems.getDefault());
+ assertEquals("conversion is incorrect", HostAccessors.getDefault().open(), upgraded);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void unsupportedExecutionSystem() throws IOException {
+ SystemUpgrader.upgrade(mock(ExecutionSystem.class));
+ }
+
+ @Test
+ public void executionSystemIsUpgradeable() {
+ assertTrue("system is not upgradeable", SystemUpgrader.isUpgradeable(es));
+
+ ExecutionSystem defaultEs = ExecutionSystems.getDefault();
+ assertTrue("system is not upgradeable", SystemUpgrader.isUpgradeable(defaultEs));
+ }
+}
diff --git a/fs-base/src/main/java/com/palantir/giraffe/file/base/BaseFileSystem.java b/fs-base/src/main/java/com/palantir/giraffe/file/base/BaseFileSystem.java
index eb944f36..4f117e27 100644
--- a/fs-base/src/main/java/com/palantir/giraffe/file/base/BaseFileSystem.java
+++ b/fs-base/src/main/java/com/palantir/giraffe/file/base/BaseFileSystem.java
@@ -15,7 +15,6 @@
*/
package com.palantir.giraffe.file.base;
-import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
@@ -30,7 +29,6 @@
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
@@ -46,60 +44,9 @@
*/
public abstract class BaseFileSystem
> extends FileSystem {
- private final AtomicBoolean closed = new AtomicBoolean();
- private final CloseableRegistry closeableRegistry = new CloseableRegistry();
-
@Override
public abstract BaseFileSystemProvider
provider();
- @Override
- public final void close() throws IOException {
- if (closed.compareAndSet(false, true)) {
- try {
- closeableRegistry.close();
- doClose();
- } finally {
- provider().fileSystemClosed(this);
- }
- }
- }
-
- /**
- * Performs any implementation-specific close actions. By default, this
- * method does nothing. Any registered resources are closed before this
- * method is called.
- *
- * @throws IOException if an I/O error occurs
- */
- protected void doClose() throws IOException {
- // do nothing by default
- }
-
- /**
- * Registers a {@link Closeable} resource with the default priority of
- * {@code 0}.
- */
- public C registerCloseable(C closeable) {
- closeableRegistry.register(closeable);
- return closeable;
- }
-
- /**
- * Registers a {@link Closeable} resource with the given priority.
- */
- public C registerCloseable(C closeable, int priority) {
- closeableRegistry.register(closeable, priority);
- return closeable;
- }
-
- /**
- * Unregisters a {@link Closeable} resource, usually after it has been
- * closed.
- */
- public void unregisterCloseable(Closeable closeable) {
- closeableRegistry.unregister(closeable);
- }
-
/**
* Returns the default directory of this file system. The default directory
* is the directory against which all relative paths are resolved.
@@ -129,11 +76,6 @@ public P getPath(String first, String... more) {
*/
protected abstract P getPath(String path);
- @Override
- public final boolean isOpen() {
- return !closed.get();
- }
-
@Override
public PathMatcher getPathMatcher(String syntaxAndPattern) {
return StandardPathMatchers.fromSyntaxAndPattern(syntaxAndPattern, getSeparator());
diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java b/ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java
index 667f6896..3c3e9d7d 100644
--- a/ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java
+++ b/ssh/src/main/java/com/palantir/giraffe/ssh/SshHostAccessor.java
@@ -17,18 +17,13 @@
import java.io.IOException;
import java.net.URI;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
-import com.palantir.giraffe.SystemConverter;
-import com.palantir.giraffe.command.ExecutionSystem;
-import com.palantir.giraffe.file.base.SuppressedCloseable;
-import com.palantir.giraffe.host.AbstractHostControlSystem;
import com.palantir.giraffe.host.AuthenticatedHostAccessor;
import com.palantir.giraffe.host.Host;
import com.palantir.giraffe.host.HostControlSystem;
+import com.palantir.giraffe.ssh.internal.SshHostControlSystem;
import com.palantir.giraffe.ssh.internal.SshUris;
/**
@@ -104,23 +99,9 @@ public SshSystemRequest request() {
@Override
public HostControlSystem open() throws IOException {
- FileSystem fs = FileSystems.newFileSystem(
- request.fileSystemUri(),
- request.options(),
- getClass().getClassLoader());
-
- // use the file system as the canonical system source
- return new SshHostControlSystem(request.uri(), fs, SystemConverter.asExecutionSystem(fs));
- }
-
- private static final class SshHostControlSystem extends AbstractHostControlSystem {
- private SshHostControlSystem(URI uri, FileSystem fs, ExecutionSystem es) {
- super(uri, fs, es);
- }
-
- @Override
- public void close() throws IOException {
- SuppressedCloseable.create(getExecutionSystem(), getFileSystem()).close();
- }
+ return SshHostControlSystem.builder(request)
+ .setFileSystem()
+ .setExecutionSystem()
+ .build();
}
}
diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/SshSystemRequest.java b/ssh/src/main/java/com/palantir/giraffe/ssh/SshSystemRequest.java
index 17818546..cd0bb4d2 100644
--- a/ssh/src/main/java/com/palantir/giraffe/ssh/SshSystemRequest.java
+++ b/ssh/src/main/java/com/palantir/giraffe/ssh/SshSystemRequest.java
@@ -77,12 +77,4 @@ public void setLogger(Logger logger) {
public String getUsername() {
return getCredential().getUsername();
}
-
- public URI fileSystemUri() {
- return SshUris.replaceScheme(uri(), SshUris.getFileScheme());
- }
-
- public URI executionSystemUri() {
- return SshUris.replaceScheme(uri(), SshUris.getExecScheme());
- }
}
diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/CloseContext.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/CloseContext.java
new file mode 100644
index 00000000..eb1db858
--- /dev/null
+++ b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/CloseContext.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2015 Palantir Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.palantir.giraffe.ssh.internal;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.palantir.giraffe.file.base.CloseableRegistry;
+
+final class CloseContext implements Closeable {
+
+ private final AtomicBoolean closed = new AtomicBoolean();
+ private final CloseableRegistry registry = new CloseableRegistry();
+
+ public void registerCloseable(Closeable closeable) {
+ registry.register(closeable);
+ }
+
+ public void registerCloseable(Closeable closeable, int priority) {
+ registry.register(closeable, priority);
+ }
+
+ public void unregister(Closeable closeable) {
+ registry.unregister(closeable);
+ }
+
+ public boolean isClosed() {
+ return closed.get();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (closed.compareAndSet(false, true)) {
+ registry.close();
+ }
+ }
+
+}
diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/InternalSshSystemRequest.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/InternalSshSystemRequest.java
index 25079e2e..4b3ffb83 100644
--- a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/InternalSshSystemRequest.java
+++ b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/InternalSshSystemRequest.java
@@ -21,9 +21,13 @@
import com.palantir.giraffe.ssh.SshSystemRequest;
+import net.schmizz.sshj.SSHClient;
+
class InternalSshSystemRequest extends SshSystemRequest {
- public static final String CLIENT_KEY = "ssh-client";
+ public static final String SOURCE_KEY = "giraffe.internal.source";
+ public static final String CLIENT_KEY = "giraffe.internal.sshClient";
+ public static final String CLOSE_CTX_KEY = "giraffe.internal.closeContext";
public InternalSshSystemRequest(URI uri, Map env) {
super(SshUris.replaceScheme(uri, SshUris.getHostScheme()), env);
@@ -33,18 +37,48 @@ public InternalSshSystemRequest(SshSystemRequest request) {
this(request.uri(), request.options());
}
- public void setClientIfMissing(SshConnectionFactory factory) throws IOException {
+ public SSHClient getClient() {
+ return get(CLIENT_KEY, SSHClient.class);
+ }
+
+ public boolean setClientIfMissing(SshConnectionFactory factory) throws IOException {
if (!contains(CLIENT_KEY)) {
- SharedSshClient client = new SharedSshClient(factory.newAuthedConnection(this));
+ SSHClient client = factory.newAuthedConnection(this);
set(CLIENT_KEY, client);
+ return true;
} else {
// call getClient() for type check
getClient();
+ return false;
+ }
+ }
+
+ public boolean isInsternalSource() {
+ if (contains(SOURCE_KEY)) {
+ return get(SOURCE_KEY, Class.class).equals(SshHostControlSystem.class);
+ } else {
+ return false;
}
}
- public SharedSshClient getClient() {
- return get(CLIENT_KEY, SharedSshClient.class);
+ public void setSource(Class> sourceClass) {
+ set(SOURCE_KEY, sourceClass);
+ }
+
+ public CloseContext getCloseContext() {
+ return get(CLOSE_CTX_KEY, CloseContext.class);
+ }
+
+ public void setCloseContext(CloseContext context) {
+ set(CLOSE_CTX_KEY, context);
+ }
+
+ public URI fileSystemUri() {
+ return SshUris.replaceScheme(uri(), SshUris.getFileScheme());
+ }
+
+ public URI executionSystemUri() {
+ return SshUris.replaceScheme(uri(), SshUris.getExecScheme());
}
}
diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SharedSshClient.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SharedSshClient.java
deleted file mode 100644
index 098d16d4..00000000
--- a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SharedSshClient.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright 2015 Palantir Technologies, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.palantir.giraffe.ssh.internal;
-
-import java.io.Closeable;
-import java.io.IOException;
-
-import javax.annotation.concurrent.GuardedBy;
-
-import net.schmizz.sshj.SSHClient;
-
-/**
- * Ensures that a {@link SSHClient} used by both a file system and execution
- * system is only closed when both systems are closed.
- *
- * @author bkeyes
- */
-class SharedSshClient implements Closeable {
-
- private final SSHClient client;
-
- @GuardedBy("this")
- private int users = 1;
-
- public SharedSshClient(SSHClient client) {
- this.client = client;
- }
-
- /**
- * Increments the number of users using this client.
- *
- * @return {@code true} if the increment was successful, {@code false} if
- * the connection is already closed.
- */
- public synchronized boolean addUser() {
- if (users == 0) {
- return false;
- } else {
- users++;
- return true;
- }
- }
-
- public SSHClient getClient() {
- return client;
- }
-
- @Override
- public void close() throws IOException {
- boolean close = false;
-
- synchronized (this) {
- if (users > 0) {
- users--;
- close = (users == 0);
- }
- }
-
- if (close) {
- client.close();
- }
- }
-
-}
diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshExecutionSystem.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshExecutionSystem.java
index 063ca454..315075c3 100644
--- a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshExecutionSystem.java
+++ b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshExecutionSystem.java
@@ -15,13 +15,11 @@
*/
package com.palantir.giraffe.ssh.internal;
+import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
@@ -29,31 +27,42 @@
import com.palantir.giraffe.command.CommandContext;
import com.palantir.giraffe.command.CommandFuture;
import com.palantir.giraffe.command.ExecutionSystem;
-import com.palantir.giraffe.file.FileSystemConvertible;
import com.palantir.giraffe.host.Host;
+import com.palantir.giraffe.host.HostControlSystem;
+import com.palantir.giraffe.host.HostControlSystemUpgradeable;
import com.palantir.giraffe.internal.CommandFutureTask;
-final class SshExecutionSystem extends ExecutionSystem implements FileSystemConvertible {
+import net.schmizz.sshj.SSHClient;
+
+final class SshExecutionSystem extends ExecutionSystem implements HostControlSystemUpgradeable {
- private final AtomicBoolean closed;
private final SshExecutionSystemProvider provider;
private final URI uri;
- private final SharedSshClient client;
+ private final SSHClient client;
private final Logger logger;
+ private final CloseContext closeContext;
+
private final ExecutorService executor;
- private final InternalSshSystemRequest request;
+
+ private SshHostControlSystem sourceSystem;
protected SshExecutionSystem(SshExecutionSystemProvider provider,
InternalSshSystemRequest request) {
this.provider = provider;
- this.request = request;
this.uri = request.executionSystemUri();
this.client = request.getClient();
this.logger = HostLogger.create(request.getLogger(), Host.fromUri(uri));
- closed = new AtomicBoolean();
+ closeContext = request.getCloseContext();
executor = Executors.newCachedThreadPool();
+
+ closeContext.registerCloseable(new Closeable() {
+ @Override
+ public void close() {
+ executor.shutdownNow();
+ }
+ });
}
@Override
@@ -68,15 +77,12 @@ public SshExecutionSystemProvider provider() {
@Override
public void close() throws IOException {
- if (closed.compareAndSet(false, true)) {
- client.close();
- executor.shutdownNow();
- }
+ closeContext.close();
}
@Override
public boolean isOpen() {
- return !closed.get();
+ return !closeContext.isClosed();
}
@Override
@@ -84,15 +90,16 @@ public URI uri() {
return uri;
}
+ void setSourceSystem(SshHostControlSystem sourceSystem) {
+ this.sourceSystem = sourceSystem;
+ }
+
@Override
- public FileSystem asFileSystem() throws IOException {
- if (client.addUser()) {
- URI fileUri = SshUris.replaceScheme(uri, SshUris.getFileScheme());
- return FileSystems.newFileSystem(
- fileUri, request.options(), getClass().getClassLoader());
- } else {
- throw new ClosedExecutionSystemException();
- }
+ public HostControlSystem asHostControlSystem() throws IOException {
+ checkOpen();
+
+ assert sourceSystem != null : "source HostControlSystem was never set";
+ return sourceSystem.asView();
}
protected Logger logger() {
@@ -106,6 +113,12 @@ protected CommandFuture execute(SshCommand command, CommandContext context) {
}
private CommandFutureTask newFutureTask(SshCommand command, CommandContext context) {
- return new SshCommandFuture(command, context, client.getClient(), executor);
+ return new SshCommandFuture(command, context, client, executor);
+ }
+
+ private void checkOpen() {
+ if (!isOpen()) {
+ throw new ClosedExecutionSystemException();
+ }
}
}
diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshExecutionSystemProvider.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshExecutionSystemProvider.java
index cad19c26..a42b0531 100644
--- a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshExecutionSystemProvider.java
+++ b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshExecutionSystemProvider.java
@@ -29,8 +29,6 @@
import com.palantir.giraffe.command.ExecutionSystemNotFoundException;
import com.palantir.giraffe.command.spi.ExecutionSystemProvider;
-import net.schmizz.sshj.DefaultConfig;
-
/**
* Provides access to remote execution systems using SSH.
*
@@ -38,12 +36,6 @@
*/
public final class SshExecutionSystemProvider extends ExecutionSystemProvider {
- private final SshConnectionFactory connectionFactory;
-
- public SshExecutionSystemProvider() {
- connectionFactory = new SshConnectionFactory(new DefaultConfig());
- }
-
@Override
public String getScheme() {
return SshUris.getExecScheme();
@@ -52,10 +44,17 @@ public String getScheme() {
@Override
public ExecutionSystem newExecutionSystem(URI uri, Map env) throws IOException {
SshUris.checkExecUri(uri);
-
InternalSshSystemRequest request = new InternalSshSystemRequest(uri, env);
- request.setClientIfMissing(connectionFactory);
- return new SshExecutionSystem(this, request);
+ if (request.isInsternalSource()) {
+ // this is being requested as part of HostControlSystem creation
+ return new SshExecutionSystem(this, request);
+ } else {
+ return SshHostControlSystem.builder(request)
+ .setFileSystem()
+ .setExecutionSystem(this)
+ .build()
+ .getExecutionSystem();
+ }
}
@Override
diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshFileSystem.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshFileSystem.java
index 7ad70fdf..db600634 100644
--- a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshFileSystem.java
+++ b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshFileSystem.java
@@ -15,6 +15,7 @@
*/
package com.palantir.giraffe.ssh.internal;
+import java.io.Closeable;
import java.io.IOError;
import java.io.IOException;
import java.net.URI;
@@ -48,14 +49,15 @@
import com.palantir.giraffe.command.CommandResult;
import com.palantir.giraffe.command.Commands;
import com.palantir.giraffe.command.ExecutionSystem;
-import com.palantir.giraffe.command.ExecutionSystemConvertible;
-import com.palantir.giraffe.command.ExecutionSystems;
import com.palantir.giraffe.file.base.BaseFileSystem;
import com.palantir.giraffe.file.base.attribute.ChmodFilePermissions;
import com.palantir.giraffe.file.base.attribute.FileAttributeViewRegistry;
import com.palantir.giraffe.file.base.attribute.PosixFileAttributeViews;
import com.palantir.giraffe.host.Host;
+import com.palantir.giraffe.host.HostControlSystem;
+import com.palantir.giraffe.host.HostControlSystemUpgradeable;
+import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.FileAttributes;
import net.schmizz.sshj.sftp.FileMode.Type;
import net.schmizz.sshj.sftp.RemoteResourceInfo;
@@ -64,35 +66,35 @@
import net.schmizz.sshj.sftp.SFTPException;
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
-final class SshFileSystem extends BaseFileSystem implements ExecutionSystemConvertible {
+final class SshFileSystem extends BaseFileSystem implements HostControlSystemUpgradeable {
public static final String SEPARATOR = "/";
private final SshFileSystemProvider provider;
private final URI uri;
- private final SharedSshClient client;
+ private final SSHClient client;
private final Logger logger;
private final FileAttributeViewRegistry viewRegistry;
- private final InternalSshSystemRequest request;
+ private final CloseContext closeContext;
private volatile SshPath defaultDirectory;
+ private SshHostControlSystem sourceSystem;
+
SshFileSystem(SshFileSystemProvider provider, InternalSshSystemRequest request) {
this.provider = provider;
- this.request = request;
this.uri = request.fileSystemUri();
this.client = request.getClient();
this.logger = HostLogger.create(request.getLogger(), Host.fromUri(uri));
- // always close the connection last
- registerCloseable(client, Integer.MAX_VALUE);
-
SshFileAttributeViewFactory factory = new SshFileAttributeViewFactory(provider);
viewRegistry = FileAttributeViewRegistry.builder()
.add(factory.getBasicFactory())
.add(factory.getPosixFactory())
.build();
+
+ closeContext = request.getCloseContext();
}
@Override
@@ -100,6 +102,16 @@ public SshFileSystemProvider provider() {
return provider;
}
+ @Override
+ public void close() throws IOException {
+ closeContext.close();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return !closeContext.isClosed();
+ }
+
@Override
public SshPath defaultDirectory() {
if (defaultDirectory == null) {
@@ -112,43 +124,39 @@ public SshPath defaultDirectory() {
return defaultDirectory;
}
+ void setSourceSystem(SshHostControlSystem sourceSystem) {
+ this.sourceSystem = sourceSystem;
+ }
+
@Override
- public ExecutionSystem asExecutionSystem() throws IOException {
- if (client.addUser()) {
- URI execUri = SshUris.replaceScheme(uri, SshUris.getExecScheme());
- return ExecutionSystems.newExecutionSystem(
- execUri, request.options(), getClass().getClassLoader());
- } else {
- throw new ClosedFileSystemException();
- }
+ public HostControlSystem asHostControlSystem() throws IOException {
+ checkOpen();
+
+ assert sourceSystem != null : "source HostControlSystem was never set";
+ return sourceSystem.asView();
}
- public CommandResult execute(String executable, Object... args) throws IOException {
+ CommandResult execute(String executable, Object... args) throws IOException {
return execute(executable, Arrays.asList(args));
}
CommandResult execute(String executable, List