Introduce CMS JShell
[lgpl/argeo-commons.git] / org.argeo.cms.jshell / src / org / argeo / cms / jshell / LocalJShellSession.java
diff --git a/org.argeo.cms.jshell/src/org/argeo/cms/jshell/LocalJShellSession.java b/org.argeo.cms.jshell/src/org/argeo/cms/jshell/LocalJShellSession.java
new file mode 100644 (file)
index 0000000..fce330d
--- /dev/null
@@ -0,0 +1,126 @@
+package org.argeo.cms.jshell;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.StandardProtocolFamily;
+import java.net.URI;
+import java.net.UnixDomainSocketAddress;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.StringJoiner;
+import java.util.UUID;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.internal.cms.jshell.osgi.OsgiExecutionControlProvider;
+
+import jdk.jshell.tool.JavaShellToolBuilder;
+
+class LocalJShellSession implements Runnable {
+       private final static CmsLog log = CmsLog.getLog(LocalJShellSession.class);
+
+       private UUID uuid;
+       private Path sessionDir;
+
+       private String fromBundle = "org.argeo.cms.jshell";
+
+       private Path stdioPath;
+       private Path stderrPath;
+       private Path cmdioPath;
+
+       private Thread replThread;
+
+       LocalJShellSession(Path sessionDir) {
+               this.sessionDir = sessionDir;
+               this.uuid = UUID.fromString(sessionDir.getFileName().toString());
+
+               stdioPath = sessionDir.resolve(JShellClient.STDIO);
+
+               replThread = new Thread(this, "JShell " + sessionDir);
+               replThread.start();
+       }
+
+       public void run() {
+               log.debug(() -> "Started JShell session " + sessionDir);
+               try (SocketPipeMirror std = new SocketPipeMirror()) {
+                       // prepare jshell tool builder
+                       JavaShellToolBuilder builder = JavaShellToolBuilder.builder();
+                       builder.in(std.getInputStream(), null);
+                       builder.interactiveTerminal(true);
+                       builder.out(new PrintStream(std.getOutputStream()));
+
+                       // UnixDomainSocketAddress ioSocketAddress = JSchellClient.ioSocketAddress();
+                       // Files.deleteIfExists(ioSocketAddress.getPath());
+                       UnixDomainSocketAddress stdSocketAddress = UnixDomainSocketAddress.of(stdioPath);
+
+                       try (ServerSocketChannel stdServerChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
+                               stdServerChannel.bind(stdSocketAddress);
+                               try (SocketChannel channel = stdServerChannel.accept()) {
+                                       std.open(channel);
+
+                                       String frameworkLocation = System.getProperty("osgi.framework");
+                                       StringJoiner classpath = new StringJoiner(File.pathSeparator);
+                                       classpath.add(Paths.get(URI.create(frameworkLocation)).toAbsolutePath().toString());
+
+                                       ClassLoader cmsJShellBundleCL = OsgiExecutionControlProvider.class.getClassLoader();
+                                       ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+                                       try {
+                                               // we need our own class loader so that Java service loader
+                                               // finds our ExecutionControlProvider implementation
+                                               Thread.currentThread().setContextClassLoader(cmsJShellBundleCL);
+                                               //
+                                               // START JSHELL
+                                               //
+                                               int exitCode = builder.start("--execution", "osgi:bundle(" + fromBundle + ")", "--class-path",
+                                                               classpath.toString());
+                                               //
+                                               log.debug("JShell " + sessionDir + " completed with exit code " + exitCode);
+                                       } finally {
+                                               Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+                                       }
+                               }
+                       }
+               } catch (Exception e) {
+                       throw new RuntimeException("JShell " + sessionDir + " failed", e);
+               } finally {
+                       cleanUp();
+               }
+       }
+
+       void cleanUp() {
+               try {
+                       if (Files.exists(stdioPath))
+                               Files.delete(stdioPath);
+                       if (Files.exists(sessionDir))
+                               Files.delete(sessionDir);
+               } catch (IOException e) {
+                       log.error("Cannot clean up JShell " + sessionDir, e);
+               }
+       }
+
+//             void addChild(Path p) throws IOException {
+//                     if (replThread != null)
+//                             throw new IllegalStateException("JShell " + sessionDir + " is already started");
+//
+//                     if (STDIO.equals(p.getFileName().toString())) {
+//                             stdioPath = p;
+//                     } else if (STDERR.equals(p.getFileName().toString())) {
+//                             stderrPath = p;
+//                     } else if (CMDIO.equals(p.getFileName().toString())) {
+//                             cmdioPath = p;
+//                     } else {
+//                             log.warn("Unkown file name " + p.getFileName() + " in " + sessionDir);
+//                     }
+//
+//                     // check that all paths are available
+//                     // if (stdioPath != null && stderrPath != null && cmdioPath != null) {
+//                     if (stdioPath != null) {
+//                             replThread = new Thread(this, "JShell " + sessionDir);
+//                             replThread.start();
+//                     }
+//             }
+
+}