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;
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();
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) {
}
}
- 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();
-// }
-// }
-
}