]> git.argeo.org Git - lgpl/argeo-commons.git/blobdiff - org.argeo.cms.jshell/src/org/argeo/cms/jshell/LocalJShellSession.java
Merge tag 'v2.3.17' into testing
[lgpl/argeo-commons.git] / org.argeo.cms.jshell / src / org / argeo / cms / jshell / LocalJShellSession.java
index 786ee272df87d23bebcb29056c40741be0974ac7..e77f653b7ce63a394a55546957461b498baa8119 100644 (file)
@@ -1,17 +1,15 @@
 package org.argeo.cms.jshell;
 
-import java.io.File;
+import static java.net.StandardProtocolFamily.UNIX;
+
 import java.io.IOException;
+import java.io.OutputStream;
 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 java.util.concurrent.Executors;
 
@@ -21,41 +19,59 @@ import javax.security.auth.login.LoginException;
 import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.util.CurrentSubject;
+import org.argeo.cms.util.FsUtils;
 import org.argeo.internal.cms.jshell.osgi.OsgiExecutionControlProvider;
 
 import jdk.jshell.tool.JavaShellToolBuilder;
 
+/** A JShell session based on local UNIX sockets. */
 class LocalJShellSession implements Runnable {
        private final static CmsLog log = CmsLog.getLog(LocalJShellSession.class);
 
        private UUID uuid;
        private Path sessionDir;
+       private Path socketsDir;
 
-       private String fromBundle = "eu.netiket.on.apaf.project.togo2023";
-
-       private Path stdioPath;
-       private Path stderrPath;
-       private Path cmdioPath;
+       private Path stdPath;
+       private Path ctlPath;
 
        private Thread replThread;
 
        private LoginContext loginContext;
 
-       LocalJShellSession(Path sessionDir) {
-               this.sessionDir = sessionDir;
-               this.uuid = UUID.fromString(sessionDir.getFileName().toString());
+       private Long bundleId;
 
-               stdioPath = sessionDir.resolve(JShellClient.STDIO);
+       private final boolean interactive;
 
-               // TODO proper login
+       LocalJShellSession(Path sessionDir, Path bundleIdDir, boolean interactive) {
+               this.interactive = interactive;
                try {
-                       loginContext = new LoginContext(CmsAuth.DATA_ADMIN.getLoginContextName());
-                       loginContext.login();
-               } catch (LoginException e1) {
-                       throw new RuntimeException("Could not login as data admin", e1);
-               } finally {
-               }
+                       this.sessionDir = sessionDir;
+                       this.uuid = UUID.fromString(sessionDir.getFileName().toString());
+                       bundleId = Long.parseLong(bundleIdDir.getFileName().toString());
+                       socketsDir = bundleIdDir.resolve(uuid.toString());
+                       Files.createDirectories(socketsDir);
+
+                       stdPath = socketsDir.resolve(JShellClient.STD);
+                       Files.createSymbolicLink(sessionDir.resolve(JShellClient.STD), stdPath);
+
+                       ctlPath = socketsDir.resolve(JShellClient.CTL);
+                       Files.createSymbolicLink(sessionDir.resolve(JShellClient.CTL), ctlPath);
+
+                       // TODO proper login
+                       try {
+                               loginContext = new LoginContext(CmsAuth.DATA_ADMIN.getLoginContextName());
+                               loginContext.login();
+                       } catch (LoginException e1) {
+                               throw new RuntimeException("Could not login as data admin", e1);
+                       } finally {
+                       }
 
+               } catch (IOException e) {
+                       log.error("Cannot initiate local session " + uuid, e);
+                       cleanUp();
+                       return;
+               }
                replThread = new Thread(() -> CurrentSubject.callAs(loginContext.getSubject(), Executors.callable(this)),
                                "JShell " + sessionDir);
                replThread.start();
@@ -64,42 +80,58 @@ class LocalJShellSession implements Runnable {
        public void run() {
 
                log.debug(() -> "Started JShell session " + sessionDir);
-               try (SocketPipeMirror std = new SocketPipeMirror()) {
+               try (SocketPipeMirror std = new SocketPipeMirror(JShellClient.STD + " " + uuid); //
+                               SocketPipeMirror ctl = new SocketPipeMirror(JShellClient.CTL + " " + uuid);) {
                        // prepare jshell tool builder
+                       String feedbackMode;
                        JavaShellToolBuilder builder = JavaShellToolBuilder.builder();
-                       builder.in(std.getInputStream(), null);
-                       builder.interactiveTerminal(true);
-                       builder.out(new PrintStream(std.getOutputStream()));
+                       if (interactive) {
+                               builder.in(std.getInputStream(), null);
+                               builder.out(new PrintStream(std.getOutputStream()));
+                               builder.err(new PrintStream(ctl.getOutputStream()));
+                               builder.interactiveTerminal(true);
+                               feedbackMode = "concise";
+                       } else {
+                               builder.in(ctl.getInputStream(), std.getInputStream());
+                               PrintStream cmdOut = new PrintStream(ctl.getOutputStream());
+                               PrintStream discard = new PrintStream(OutputStream.nullOutputStream());
+                               builder.out(cmdOut, discard, new PrintStream(std.getOutputStream()));
+                               builder.err(cmdOut);
+                               builder.promptCapture(true);
+                               feedbackMode = "silent";
+                       }
 
-                       // UnixDomainSocketAddress ioSocketAddress = JSchellClient.ioSocketAddress();
-                       // Files.deleteIfExists(ioSocketAddress.getPath());
-                       UnixDomainSocketAddress stdSocketAddress = UnixDomainSocketAddress.of(stdioPath);
+                       UnixDomainSocketAddress stdSocketAddress = UnixDomainSocketAddress.of(stdPath);
+                       UnixDomainSocketAddress ctlSocketAddress = UnixDomainSocketAddress.of(ctlPath);
 
-                       try (ServerSocketChannel stdServerChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
+                       try (ServerSocketChannel stdServerChannel = ServerSocketChannel.open(UNIX);
+                                       ServerSocketChannel ctlServerChannel = ServerSocketChannel.open(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());
+                               ctlServerChannel.bind(ctlSocketAddress);
+                               try (SocketChannel stdChannel = stdServerChannel.accept();
+                                               SocketChannel ctlChannel = ctlServerChannel.accept();) {
+                                       std.open(stdChannel);
+                                       ctl.open(ctlChannel);
 
                                        ClassLoader cmsJShellBundleCL = OsgiExecutionControlProvider.class.getClassLoader();
                                        ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
                                        try {
+                                               String classpath = OsgiExecutionControlProvider.getBundleClasspath(bundleId);
+                                               Path bundleStartupScript = OsgiExecutionControlProvider.getBundleStartupScript(bundleId);
                                                // 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());
+                                               int exitCode = builder.start("--execution", "osgi:bundle(" + bundleId + ")", "--class-path",
+                                                               classpath, "--startup", bundleStartupScript.toString(), "--feedback", feedbackMode);
                                                //
                                                log.debug("JShell " + sessionDir + " completed with exit code " + exitCode);
                                        } finally {
                                                Thread.currentThread().setContextClassLoader(currentContextClassLoader);
                                        }
+                               } finally {
                                }
                        }
                } catch (Exception e) {
@@ -109,43 +141,21 @@ class LocalJShellSession implements Runnable {
                }
        }
 
-       void cleanUp() {
+       private void cleanUp() {
                try {
-                       if (Files.exists(stdioPath))
-                               Files.delete(stdioPath);
+                       if (Files.exists(socketsDir))
+                               FsUtils.delete(socketsDir);
                        if (Files.exists(sessionDir))
-                               Files.delete(sessionDir);
+                               FsUtils.delete(sessionDir);
                } catch (IOException e) {
                        log.error("Cannot clean up JShell " + sessionDir, e);
                }
 
-               try {
-                       loginContext.logout();
-               } catch (LoginException e) {
-                       log.error("Cannot log out JShell " + sessionDir, e);
-               }
+               if (loginContext != null)
+                       try {
+                               loginContext.logout();
+                       } catch (LoginException e) {
+                               log.error("Cannot log out 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();
-//                     }
-//             }
-
 }