1 package org
.argeo
.cms
.jshell
;
3 import static java
.net
.StandardProtocolFamily
.UNIX
;
5 import java
.io
.IOException
;
6 import java
.io
.OutputStream
;
7 import java
.io
.PrintStream
;
8 import java
.net
.UnixDomainSocketAddress
;
9 import java
.nio
.channels
.ServerSocketChannel
;
10 import java
.nio
.channels
.SocketChannel
;
11 import java
.nio
.file
.Files
;
12 import java
.nio
.file
.Path
;
13 import java
.util
.UUID
;
14 import java
.util
.concurrent
.Executors
;
16 import javax
.security
.auth
.login
.LoginContext
;
17 import javax
.security
.auth
.login
.LoginException
;
19 import org
.argeo
.api
.cms
.CmsAuth
;
20 import org
.argeo
.api
.cms
.CmsLog
;
21 import org
.argeo
.cms
.util
.CurrentSubject
;
22 import org
.argeo
.cms
.util
.FsUtils
;
23 import org
.argeo
.internal
.cms
.jshell
.osgi
.OsgiExecutionControlProvider
;
25 import jdk
.jshell
.tool
.JavaShellToolBuilder
;
27 /** A JShell session based on local UNIX sockets. */
28 class LocalJShellSession
implements Runnable
{
29 private final static CmsLog log
= CmsLog
.getLog(LocalJShellSession
.class);
32 private Path sessionDir
;
33 private Path socketsDir
;
38 private Thread replThread
;
40 private LoginContext loginContext
;
42 private Long bundleId
;
44 private final boolean interactive
;
46 LocalJShellSession(Path sessionDir
, Path bundleIdDir
, boolean interactive
) {
47 this.interactive
= interactive
;
49 this.sessionDir
= sessionDir
;
50 this.uuid
= UUID
.fromString(sessionDir
.getFileName().toString());
51 bundleId
= Long
.parseLong(bundleIdDir
.getFileName().toString());
52 socketsDir
= bundleIdDir
.resolve(uuid
.toString());
53 Files
.createDirectories(socketsDir
);
55 stdPath
= socketsDir
.resolve(JShellClient
.STD
);
56 Files
.createSymbolicLink(sessionDir
.resolve(JShellClient
.STD
), stdPath
);
58 ctlPath
= socketsDir
.resolve(JShellClient
.CTL
);
59 Files
.createSymbolicLink(sessionDir
.resolve(JShellClient
.CTL
), ctlPath
);
63 loginContext
= new LoginContext(CmsAuth
.DATA_ADMIN
.getLoginContextName());
65 } catch (LoginException e1
) {
66 throw new RuntimeException("Could not login as data admin", e1
);
70 } catch (IOException e
) {
71 log
.error("Cannot initiate local session " + uuid
, e
);
75 replThread
= new Thread(() -> CurrentSubject
.callAs(loginContext
.getSubject(), Executors
.callable(this)),
76 "JShell " + sessionDir
);
82 log
.debug(() -> "Started JShell session " + sessionDir
);
83 try (SocketPipeMirror std
= new SocketPipeMirror(JShellClient
.STD
+ " " + uuid
); //
84 SocketPipeMirror ctl
= new SocketPipeMirror(JShellClient
.CTL
+ " " + uuid
);) {
85 // prepare jshell tool builder
87 JavaShellToolBuilder builder
= JavaShellToolBuilder
.builder();
89 builder
.in(std
.getInputStream(), null);
90 builder
.out(new PrintStream(std
.getOutputStream()));
91 builder
.err(new PrintStream(ctl
.getOutputStream()));
92 builder
.interactiveTerminal(true);
93 feedbackMode
= "concise";
95 builder
.in(ctl
.getInputStream(), std
.getInputStream());
96 PrintStream cmdOut
= new PrintStream(ctl
.getOutputStream());
97 PrintStream discard
= new PrintStream(OutputStream
.nullOutputStream());
98 builder
.out(cmdOut
, discard
, new PrintStream(std
.getOutputStream()));
100 builder
.promptCapture(true);
101 feedbackMode
= "silent";
104 UnixDomainSocketAddress stdSocketAddress
= UnixDomainSocketAddress
.of(stdPath
);
105 UnixDomainSocketAddress ctlSocketAddress
= UnixDomainSocketAddress
.of(ctlPath
);
107 try (ServerSocketChannel stdServerChannel
= ServerSocketChannel
.open(UNIX
);
108 ServerSocketChannel ctlServerChannel
= ServerSocketChannel
.open(UNIX
);) {
109 stdServerChannel
.bind(stdSocketAddress
);
110 ctlServerChannel
.bind(ctlSocketAddress
);
111 try (SocketChannel stdChannel
= stdServerChannel
.accept();
112 SocketChannel ctlChannel
= ctlServerChannel
.accept();) {
113 std
.open(stdChannel
);
114 ctl
.open(ctlChannel
);
116 ClassLoader cmsJShellBundleCL
= OsgiExecutionControlProvider
.class.getClassLoader();
117 ClassLoader currentContextClassLoader
= Thread
.currentThread().getContextClassLoader();
119 String classpath
= OsgiExecutionControlProvider
.getBundleClasspath(bundleId
);
120 Path bundleStartupScript
= OsgiExecutionControlProvider
.getBundleStartupScript(bundleId
);
121 // we need our own class loader so that Java service loader
122 // finds our ExecutionControlProvider implementation
123 Thread
.currentThread().setContextClassLoader(cmsJShellBundleCL
);
127 int exitCode
= builder
.start("--execution", "osgi:bundle(" + bundleId
+ ")", "--class-path",
128 classpath
, "--startup", bundleStartupScript
.toString(), "--feedback", feedbackMode
);
130 log
.debug("JShell " + sessionDir
+ " completed with exit code " + exitCode
);
132 Thread
.currentThread().setContextClassLoader(currentContextClassLoader
);
137 } catch (Exception e
) {
138 throw new RuntimeException("JShell " + sessionDir
+ " failed", e
);
144 private void cleanUp() {
146 if (Files
.exists(socketsDir
))
147 FsUtils
.delete(socketsDir
);
148 if (Files
.exists(sessionDir
))
149 FsUtils
.delete(sessionDir
);
150 } catch (IOException e
) {
151 log
.error("Cannot clean up JShell " + sessionDir
, e
);
154 if (loginContext
!= null)
156 loginContext
.logout();
157 } catch (LoginException e
) {
158 log
.error("Cannot log out JShell " + sessionDir
, e
);