--- /dev/null
+package org.argeo.cms.jshell;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.util.OS;
+
+public class CmsJShell {
+ private final static CmsLog log = CmsLog.getLog(CmsJShell.class);
+ static ClassLoader loader = CmsJShell.class.getClassLoader();
+
+ public static UuidFactory uuidFactory = null;
+
+ private CmsState cmsState;
+
+ private Map<Path, LocalJShellSession> localSessions = new HashMap<>();
+
+ private Path localBase;
+ private Path linkedDir;
+
+ public void start() throws Exception {
+
+ // Path localBase = cmsState.getStatePath("org.argeo.cms.jshell/local");
+ UUID stateUuid = cmsState.getUuid();
+
+ // TODO centralise state run dir
+ Path stateRunDir = OS.getRunDir().resolve(stateUuid.toString());
+ localBase = stateRunDir.resolve("jsh");
+ Files.createDirectories(localBase);
+
+ linkedDir = Files.createSymbolicLink(cmsState.getStatePath("jsh"), localBase);
+
+ log.info("Local JShell on " + localBase + ", linked to " + linkedDir);
+
+ new Thread(() -> {
+ try {
+ WatchService watchService = FileSystems.getDefault().newWatchService();
+
+ localBase.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
+ StandardWatchEventKinds.ENTRY_DELETE);
+
+ WatchKey key;
+ while ((key = watchService.take()) != null) {
+ events: for (WatchEvent<?> event : key.pollEvents()) {
+// System.out.println("Event kind:" + event.kind() + ". File affected: " + event.context() + ".");
+ Path path = localBase.resolve((Path) event.context());
+ // sessions
+ if (Files.isSameFile(localBase, path.getParent())) {
+ if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
+ if (!Files.isDirectory(path)) {
+ log.warn("Ignoring " + path + " as it is not a directory");
+ continue events;
+ }
+ try {
+ UUID.fromString(path.getFileName().toString());
+ } catch (IllegalArgumentException e) {
+ log.warn("Ignoring " + path + " as it is not named as UUID");
+ continue events;
+ }
+
+ LocalJShellSession localSession = new LocalJShellSession(path);
+ localSessions.put(path, localSession);
+ } else if (StandardWatchEventKinds.ENTRY_DELETE.equals(event.kind())) {
+ // TODO clean up session
+ LocalJShellSession localSession = localSessions.remove(path);
+ localSession.cleanUp();
+ }
+ } else {
+// if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
+// Path sessionDir = path.getParent();
+// LocalSession session = localSessions.get(sessionDir);
+// if (session == null) {
+// sessions: for (Path p : localSessions.keySet()) {
+// if (Files.isSameFile(sessionDir, p)) {
+// session = localSessions.get(p);
+// break sessions;
+// }
+// }
+// }
+// if (session == null) {
+// log.warn("Ignoring " + path + " as its parent is not a registered session");
+// continue events;
+// }
+// session.addChild(path);
+// }
+
+ }
+ }
+ key.reset();
+ }
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ }, "JSChell local sessions watcher").start();
+
+ // thread context class loader should be where the service is defined
+// Thread.currentThread().setContextClassLoader(loader);
+// JavaShellToolBuilder builder = JavaShellToolBuilder.builder();
+//
+// builder.start("--execution", "osgi:bundle(org.argeo.cms.jshell)");
+
+ }
+
+// public void startX(BundleContext bc) {
+// uuidFactory = new NoOpUuidFactory();
+//
+// List<String> locations = new ArrayList<>();
+// for (Bundle bundle : bc.getBundles()) {
+// locations.add(bundle.getLocation());
+//// System.out.println(bundle.getLocation());
+// }
+//
+// CmsState cmsState = (CmsState) bc.getService(bc.getServiceReference("org.argeo.api.cms.CmsState"));
+// System.out.println(cmsState.getDeployProperties(CmsDeployProperty.HTTP_PORT.getProperty()));
+// System.out.println(cmsState.getUuid());
+//
+// ExecutionControlProvider executionControlProvider = new ExecutionControlProvider() {
+// @Override
+// public String name() {
+// return "name";
+// }
+//
+// @Override
+// public ExecutionControl generate(ExecutionEnv ee, Map<String, String> map) throws Throwable {
+// return new LocalExecutionControl(new WrappingLoaderDelegate(loader));
+//// Thread.currentThread().setContextClassLoader(loader);
+//// return new DirectExecutionControl();
+// }
+// };
+//
+//// Thread.currentThread().setContextClassLoader(loader);
+//
+// try (JShell js = JShell.builder().executionEngine(executionControlProvider, null).build()) {
+// js.addToClasspath("/home/mbaudier/dev/git/unstable/output/a2/org.argeo.cms/org.argeo.api.cms.2.3.jar");
+// js.addToClasspath("/home/mbaudier/dev/git/unstable/output/a2/org.argeo.cms/org.argeo.cms.2.3.jar");
+// js.addToClasspath(
+// "/home/mbaudier/dev/git/unstable/output/a2/osgi/equinox/org.argeo.tp.osgi/org.eclipse.osgi.3.18.jar");
+//// do {
+// System.out.print("Enter some Java code: ");
+// // String input = console.readLine();
+// String imports = """
+// import org.argeo.api.cms.*;
+// import org.argeo.cms.*;
+// import org.argeo.slc.jshell.*;
+// """;
+// js.eval(imports);
+// String input = """
+// var bc = org.osgi.framework.FrameworkUtil.getBundle(org.argeo.cms.CmsDeployProperty.class).getBundleContext();
+// var cmsState =(org.argeo.api.cms.CmsState) bc.getService(bc.getServiceReference("org.argeo.api.cms.CmsState"));
+// System.out.println(cmsState.getDeployProperties(org.argeo.cms.CmsDeployProperty.HTTP_PORT.getProperty()));
+// cmsState.getUuid();
+// """;
+//// if (input == null) {
+//// break;
+//// }
+//
+// input.lines().forEach((l) -> {
+//
+// List<SnippetEvent> events = js.eval(l);
+// for (SnippetEvent e : events) {
+// StringBuilder sb = new StringBuilder();
+// if (e.causeSnippet() == null) {
+// // We have a snippet creation event
+// switch (e.status()) {
+// case VALID:
+// sb.append("Successful ");
+// break;
+// case RECOVERABLE_DEFINED:
+// sb.append("With unresolved references ");
+// break;
+// case RECOVERABLE_NOT_DEFINED:
+// sb.append("Possibly reparable, failed ");
+// break;
+// case REJECTED:
+// sb.append("Failed ");
+// break;
+// }
+// if (e.previousStatus() == Status.NONEXISTENT) {
+// sb.append("addition");
+// } else {
+// sb.append("modification");
+// }
+// sb.append(" of ");
+// sb.append(e.snippet().source());
+// System.out.println(sb);
+// if (e.value() != null) {
+// System.out.printf("Value is: %s\n", e.value());
+// }
+// System.out.flush();
+// }
+// }
+// });
+//// } while (true);
+// }
+// }
+
+ public void stop() {
+ try {
+ Files.delete(linkedDir);
+ } catch (IOException e) {
+ log.error("Cannot remove " + linkedDir);
+ }
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+// public static void main(String[] args) throws Exception {
+// Pipe inPipe = Pipe.open();
+// Pipe outPipe = Pipe.open();
+//
+// InputStream in = Channels.newInputStream(inPipe.source());
+// OutputStream out = Channels.newOutputStream(outPipe.sink());
+// JavaShellToolBuilder builder = JavaShellToolBuilder.builder();
+// builder.in(in, null);
+// builder.interactiveTerminal(true);
+// builder.out(new PrintStream(out));
+//
+// UnixDomainSocketAddress ioSocketAddress = JShellClient.ioSocketAddress();
+// Files.deleteIfExists(ioSocketAddress.getPath());
+//
+// try (ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
+// serverChannel.bind(ioSocketAddress);
+//
+// try (SocketChannel channel = serverChannel.accept()) {
+// new Thread(() -> {
+//
+// try {
+// ByteBuffer buffer = ByteBuffer.allocate(1024);
+// while (true) {
+// if (channel.read(buffer) < 0)
+// break;
+// buffer.flip();
+// inPipe.sink().write(buffer);
+// buffer.rewind();
+// }
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// }, "Read in").start();
+//
+// new Thread(() -> {
+//
+// try {
+// ByteBuffer buffer = ByteBuffer.allocate(1024);
+// while (true) {
+// if (outPipe.source().read(buffer) < 0)
+// break;
+// buffer.flip();
+// channel.write(buffer);
+// buffer.rewind();
+// }
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// }, "Write out").start();
+//
+// builder.start();
+// }
+// } finally {
+// System.out.println("Completed");
+// }
+// }
+
+}