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 args) throws IOException { - try (ExecutionSystem es = asExecutionSystem()) { - Command cmd = es.getCommandBuilder(executable).addArguments(args).build(); - return Commands.execute(cmd, CommandContext.ignoreExitStatus()); - } + ExecutionSystem es = sourceSystem.getExecutionSystem(); + Command cmd = es.getCommandBuilder(executable).addArguments(args).build(); + return Commands.execute(cmd, CommandContext.ignoreExitStatus()); } - public SFTPClient openSftpClient() throws IOException { - if (!isOpen()) { - throw new ClosedFileSystemException(); - } - return client.getClient().newSFTPClient(); + SFTPClient openSftpClient() throws IOException { + checkOpen(); + return client.newSFTPClient(); } SCPFileTransfer getScpFileTransfer() { - if (!isOpen()) { - throw new ClosedFileSystemException(); - } - return client.getClient().newSCPFileTransfer(); + checkOpen(); + return client.newSCPFileTransfer(); } - protected Logger logger() { + Logger logger() { return logger; } @@ -315,4 +323,19 @@ public Iterable getRootDirectories() { public Iterable getFileStores() { throw new UnsupportedOperationException(); } + + C registerCloseable(C closeable) { + closeContext.registerCloseable(closeable); + return closeable; + } + + void unregisterCloseable(Closeable closeable) { + closeContext.unregister(closeable); + } + + private void checkOpen() { + if (!isOpen()) { + throw new ClosedFileSystemException(); + } + } } diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshFileSystemProvider.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshFileSystemProvider.java index 6dd2a5f7..e833a4db 100644 --- a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshFileSystemProvider.java +++ b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshFileSystemProvider.java @@ -47,7 +47,6 @@ import com.palantir.giraffe.file.base.feature.RecursiveDelete; import com.palantir.giraffe.file.base.feature.RecursivePermissions; -import net.schmizz.sshj.DefaultConfig; import net.schmizz.sshj.xfer.scp.SCPFileTransfer; /** @@ -58,11 +57,8 @@ public final class SshFileSystemProvider extends BaseFileSystemProvider implements RecursiveDelete, RecursivePermissions, LargeFileCopy, RecursiveCopy { - private final SshConnectionFactory connectionFactory; - public SshFileSystemProvider() { super(SshPath.class); - connectionFactory = new SshConnectionFactory(new DefaultConfig()); } @Override @@ -73,10 +69,17 @@ public String getScheme() { @Override public FileSystem newFileSystem(URI uri, Map env) throws IOException { SshUris.checkFileUri(uri); - InternalSshSystemRequest request = new InternalSshSystemRequest(uri, env); - request.setClientIfMissing(connectionFactory); - return new SshFileSystem(this, request); + if (request.isInsternalSource()) { + // this is being requested as part of HostControlSystem creation + return new SshFileSystem(this, request); + } else { + return SshHostControlSystem.builder(request) + .setFileSystem(this) + .setExecutionSystem() + .build() + .getFileSystem(); + } } @Override diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshHostControlSystem.java b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshHostControlSystem.java new file mode 100644 index 00000000..048e2d92 --- /dev/null +++ b/ssh/src/main/java/com/palantir/giraffe/ssh/internal/SshHostControlSystem.java @@ -0,0 +1,134 @@ +/** + * 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 static com.google.common.base.Preconditions.checkNotNull; + +import java.io.IOException; +import java.nio.file.FileSystems; + +import com.palantir.giraffe.command.ExecutionSystems; +import com.palantir.giraffe.host.AbstractHostControlSystem; +import com.palantir.giraffe.host.HostControlSystem; +import com.palantir.giraffe.ssh.SshSystemRequest; + +import net.schmizz.sshj.DefaultConfig; + +/** + * A {@link HostControlSystem} implementation that uses the SSH protocol. + * + * @author bkeyes + */ +public final class SshHostControlSystem extends AbstractHostControlSystem { + + private static final class ConnectionFactoryHolder { + private static final SshConnectionFactory factory = newFactory(); + + private static SshConnectionFactory newFactory() { + return new SshConnectionFactory(new DefaultConfig()); + } + } + + public static Builder builder(SshSystemRequest request) throws IOException { + // even if request has the right type, we want a copy here + InternalSshSystemRequest internalRequest = new InternalSshSystemRequest(request); + internalRequest.setSource(SshHostControlSystem.class); + + CloseContext closeContext = new CloseContext(); + internalRequest.setCloseContext(closeContext); + + if (internalRequest.setClientIfMissing(ConnectionFactoryHolder.factory)) { + closeContext.registerCloseable(internalRequest.getClient(), Integer.MAX_VALUE); + } + + return new Builder(internalRequest); + } + + /** + * Builds {@code SshHostControlSystem} instances. + */ + public static final class Builder { + private final InternalSshSystemRequest request; + + private SshFileSystem fs; + private SshExecutionSystem es; + + private Builder(InternalSshSystemRequest request) { + this.request = request; + } + + public Builder setFileSystem(SshFileSystemProvider provider) { + fs = new SshFileSystem(provider, request); + return this; + } + + public Builder setFileSystem() throws IOException { + fs = (SshFileSystem) FileSystems.newFileSystem( + request.fileSystemUri(), request.options(), + getClass().getClassLoader()); + return this; + } + + public Builder setExecutionSystem(SshExecutionSystemProvider provider) { + es = new SshExecutionSystem(provider, request); + return this; + } + + public Builder setExecutionSystem() throws IOException { + es = (SshExecutionSystem) ExecutionSystems.newExecutionSystem( + request.executionSystemUri(), request.options(), + getClass().getClassLoader()); + return this; + } + + public SshHostControlSystem build() { + checkNotNull(fs, "file system not set"); + checkNotNull(es, "execution system not set"); + + SshHostControlSystem system = new SshHostControlSystem(this); + fs.setSourceSystem(system); + es.setSourceSystem(system); + return system; + } + } + + private final CloseContext closeContext; + + private SshHostControlSystem(Builder builder) { + super(builder.request.uri(), builder.fs, builder.es); + this.closeContext = builder.request.getCloseContext(); + } + + @Override + public void close() throws IOException { + closeContext.close(); + } + + public HostControlSystem asView() { + return new View(this); + } + + private static class View extends AbstractHostControlSystem { + View(SshHostControlSystem source) { + super(source.uri(), source.getFileSystem(), source.getExecutionSystem()); + } + + @Override + public void close() { + // do nothing + } + } +} diff --git a/ssh/src/main/java/com/palantir/giraffe/ssh/package-info.java b/ssh/src/main/java/com/palantir/giraffe/ssh/package-info.java index b60cc60a..76a50220 100644 --- a/ssh/src/main/java/com/palantir/giraffe/ssh/package-info.java +++ b/ssh/src/main/java/com/palantir/giraffe/ssh/package-info.java @@ -30,9 +30,9 @@ *

System Conversion

*

* {@code FileSystem} and {@code ExecutionSystem} instances from the SSH - * provider support conversion using {@link com.palantir.giraffe.SystemConverter} - * Converted systems share a connection with the original system. While closing - * one of these systems does not affect the other, any event that corrupts the + * provider support upgrades using {@link com.palantir.giraffe.SystemUpgrader} + * Upgrades systems share a connection with the original system. While closing + * the upgrades system does not affect the original, any event that corrupts the * connection will affect both systems. Changes made by one system are visible * to the other system provided the modification happens-before the * read. Note that the reverse is not true: detecting a modification does diff --git a/ssh/src/test/java/com/palantir/giraffe/ssh/ExecutionSystemConversionTest.java b/ssh/src/test/java/com/palantir/giraffe/ssh/ExecutionSystemUpgradeTest.java similarity index 70% rename from ssh/src/test/java/com/palantir/giraffe/ssh/ExecutionSystemConversionTest.java rename to ssh/src/test/java/com/palantir/giraffe/ssh/ExecutionSystemUpgradeTest.java index fa9f9198..266d5a65 100644 --- a/ssh/src/test/java/com/palantir/giraffe/ssh/ExecutionSystemConversionTest.java +++ b/ssh/src/test/java/com/palantir/giraffe/ssh/ExecutionSystemUpgradeTest.java @@ -19,54 +19,56 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.nio.file.FileSystem; import java.nio.file.Files; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.Test; -import com.palantir.giraffe.SystemConverter; +import com.palantir.giraffe.SystemUpgrader; import com.palantir.giraffe.command.Command; import com.palantir.giraffe.command.Commands; +import com.palantir.giraffe.command.ExecutionSystem; import com.palantir.giraffe.command.test.ExecutionSystemBaseTest; import com.palantir.giraffe.command.test.runner.ExecutionSystemTestRule; import com.palantir.giraffe.file.MoreFiles; +import com.palantir.giraffe.host.HostControlSystem; /** * Tests that SSH execution systems can be converted to file systems. * * @author bkeyes */ -public class ExecutionSystemConversionTest extends ExecutionSystemBaseTest { +public class ExecutionSystemUpgradeTest extends ExecutionSystemBaseTest { private static final String PRINTF_DATA = "giraffe"; - public ExecutionSystemConversionTest(ExecutionSystemTestRule esRule) { + public ExecutionSystemUpgradeTest(ExecutionSystemTestRule esRule) { super(esRule); } @Test - public void createsFileSystem() throws IOException { + public void createsHostControlSystem() throws IOException { Command command = getSystemCommand("pwd"); - try (FileSystem fs = SystemConverter.asFileSystem(command)) { - assertTrue("file system is not open", fs.isOpen()); + try (HostControlSystem hcs = SystemUpgrader.upgrade(command.getExecutionSystem())) { + assertTrue("file system is not open", hcs.getFileSystem().isOpen()); // ignore result, as long as this does not fail - Files.exists(MoreFiles.defaultDirectory(fs)); + Files.exists(MoreFiles.defaultDirectory(hcs.getFileSystem())); } } @Test public void systemIsIndependent() throws IOException, TimeoutException { Command printf = getSystemCommand("printf", "%s", PRINTF_DATA); + ExecutionSystem es = printf.getExecutionSystem(); - assertTrue("execution system is not open", printf.getExecutionSystem().isOpen()); + assertTrue("execution system is not open", es.isOpen()); assertEquals("incorrect output", PRINTF_DATA, getOutput(printf)); - SystemConverter.asFileSystem(printf).close(); + SystemUpgrader.upgrade(es).close(); - assertTrue("execution system is not open", printf.getExecutionSystem().isOpen()); + assertTrue("execution system is not open", es.isOpen()); assertEquals("incorrect output", PRINTF_DATA, getOutput(printf)); } diff --git a/ssh/src/test/java/com/palantir/giraffe/ssh/FileSystemConversionTest.java b/ssh/src/test/java/com/palantir/giraffe/ssh/FileSystemUpgradeTest.java similarity index 66% rename from ssh/src/test/java/com/palantir/giraffe/ssh/FileSystemConversionTest.java rename to ssh/src/test/java/com/palantir/giraffe/ssh/FileSystemUpgradeTest.java index 94f2a7f4..c64c2ac7 100644 --- a/ssh/src/test/java/com/palantir/giraffe/ssh/FileSystemConversionTest.java +++ b/ssh/src/test/java/com/palantir/giraffe/ssh/FileSystemUpgradeTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.TimeUnit; @@ -25,44 +26,45 @@ import org.junit.Test; -import com.palantir.giraffe.SystemConverter; +import com.palantir.giraffe.SystemUpgrader; import com.palantir.giraffe.command.Commands; -import com.palantir.giraffe.command.ExecutionSystem; import com.palantir.giraffe.file.test.FileSystemBaseTest; import com.palantir.giraffe.file.test.runner.FileSystemTestRule; +import com.palantir.giraffe.host.HostControlSystem; /** * Tests that SSH file systems can be converted to execution systems. * * @author bkeyes */ -public class FileSystemConversionTest extends FileSystemBaseTest { +public class FileSystemUpgradeTest extends FileSystemBaseTest { - public FileSystemConversionTest(FileSystemTestRule fsRule) { + public FileSystemUpgradeTest(FileSystemTestRule fsRule) { super(fsRule); } @Test - public void createsExecutionSystem() throws IOException, TimeoutException { + public void createsHostControlSystem() throws IOException, TimeoutException { Path root = getFileSystemRule().getTestFilesRoot(); - try (ExecutionSystem es = SystemConverter.asExecutionSystem(root)) { - assertTrue("execution system is not open", es.isOpen()); + try (HostControlSystem hcs = SystemUpgrader.upgrade(root.getFileSystem())) { + assertTrue("execution system is not open", hcs.getExecutionSystem().isOpen()); // ignore result, as long as this does not fail - Commands.execute(es.getCommand("pwd"), 10, TimeUnit.SECONDS); + Commands.execute(hcs.getCommand("pwd"), 10, TimeUnit.SECONDS); } } @Test public void systemIsIndependent() throws IOException { Path root = getFileSystemRule().getTestFilesRoot(); + FileSystem fs = root.getFileSystem(); - assertTrue("file system is not open", root.getFileSystem().isOpen()); + assertTrue("file system is not open", fs.isOpen()); assertTrue(msg(root, "does not exist"), Files.exists(root)); - SystemConverter.asExecutionSystem(root).close(); + SystemUpgrader.upgrade(fs).close(); - assertTrue("file system is not open", root.getFileSystem().isOpen()); + assertTrue("file system is not open", fs.isOpen()); assertTrue(msg(root, "does not exist"), Files.exists(root)); } diff --git a/ssh/src/test/java/com/palantir/giraffe/ssh/suite/EmbeddedSshExecutionSystemSuite.java b/ssh/src/test/java/com/palantir/giraffe/ssh/suite/EmbeddedSshExecutionSystemSuite.java index 42a81e77..8c06c57d 100644 --- a/ssh/src/test/java/com/palantir/giraffe/ssh/suite/EmbeddedSshExecutionSystemSuite.java +++ b/ssh/src/test/java/com/palantir/giraffe/ssh/suite/EmbeddedSshExecutionSystemSuite.java @@ -26,7 +26,7 @@ import com.palantir.giraffe.command.test.ExecutionSystemContextTest; import com.palantir.giraffe.command.test.ExecutionSystemIoTest; import com.palantir.giraffe.command.test.runner.ExecutionSystemTestRule; -import com.palantir.giraffe.ssh.ExecutionSystemConversionTest; +import com.palantir.giraffe.ssh.ExecutionSystemUpgradeTest; import com.palantir.giraffe.ssh.util.MinaSshdExecutionSystemRule; import com.palantir.giraffe.test.runner.SystemSuite; import com.palantir.giraffe.test.runner.SystemSuite.Filterable; @@ -43,7 +43,7 @@ ExecutionSystemIoTest.class, ExecutionSystemArgumentsTest.class, ExecutionSystemContextTest.class, - ExecutionSystemConversionTest.class + ExecutionSystemUpgradeTest.class }) public class EmbeddedSshExecutionSystemSuite implements Filterable { diff --git a/ssh/src/test/java/com/palantir/giraffe/ssh/suite/EmbeddedSshFileSystemSuite.java b/ssh/src/test/java/com/palantir/giraffe/ssh/suite/EmbeddedSshFileSystemSuite.java index 2d7febd7..635379ed 100644 --- a/ssh/src/test/java/com/palantir/giraffe/ssh/suite/EmbeddedSshFileSystemSuite.java +++ b/ssh/src/test/java/com/palantir/giraffe/ssh/suite/EmbeddedSshFileSystemSuite.java @@ -31,7 +31,7 @@ import com.palantir.giraffe.file.test.FileSystemSymlinkTest; import com.palantir.giraffe.file.test.FileSystemWriteAttributesTest; import com.palantir.giraffe.file.test.runner.FileSystemTestRule; -import com.palantir.giraffe.ssh.FileSystemConversionTest; +import com.palantir.giraffe.ssh.FileSystemUpgradeTest; import com.palantir.giraffe.ssh.util.MinaSshdFileSystemRule; import com.palantir.giraffe.test.runner.RemoveTestsFilter; import com.palantir.giraffe.test.runner.SystemSuite; @@ -54,7 +54,7 @@ FileSystemSymlinkTest.class, FileSystemCopyMoveTest.class, FileSystemProviderMismatchTest.class, - FileSystemConversionTest.class + FileSystemUpgradeTest.class }) public class EmbeddedSshFileSystemSuite implements Filterable {