Make JShell bundle classpath more robust
[lgpl/argeo-commons.git] / org.argeo.cms.jshell / src / org / argeo / internal / cms / jshell / osgi / OsgiExecutionControlProvider.java
index 80acd55517e9af81e2b7a990584603458877397c..9ebf97ed07be957c25203e409a9272986a9badf5 100644 (file)
@@ -19,8 +19,10 @@ import java.util.TreeMap;
 import java.util.TreeSet;
 
 import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.jshell.CmsExecutionControl;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.Version;
 import org.osgi.framework.namespace.PackageNamespace;
@@ -28,7 +30,6 @@ import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.BundleWire;
 import org.osgi.framework.wiring.BundleWiring;
 
-import jdk.jshell.execution.DirectExecutionControl;
 import jdk.jshell.spi.ExecutionControl;
 import jdk.jshell.spi.ExecutionControlProvider;
 import jdk.jshell.spi.ExecutionEnv;
@@ -53,24 +54,15 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
 
        @Override
        public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters) throws Throwable {
-               // TODO find a better way to get a default bundle context
-               // NOTE: the related default bundle has to be started
-
-//             String symbolicName = parameters.get(BUNDLE_PARAMETER);
-//             Bundle fromBundle = getBundleFromSn(symbolicName);
-
                Long bundleId = Long.parseLong(parameters.get(BUNDLE_PARAMETER));
                Bundle fromBundle = getBundleFromId(bundleId);
 
                BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
                ClassLoader fromBundleClassLoader = fromBundleWiring.getClassLoader();
 
-               // use the bundle classloade as context classloader
-               Thread.currentThread().setContextClassLoader(fromBundleClassLoader);
-
-               ExecutionControl executionControl = new DirectExecutionControl(
+               ExecutionControl executionControl = new CmsExecutionControl(env,
                                new WrappingLoaderDelegate(env, fromBundleClassLoader));
-               log.debug("JShell from " + fromBundle.getSymbolicName() + "_" + fromBundle.getVersion() + " ["
+               log.trace(() -> "JShell from " + fromBundle.getSymbolicName() + "_" + fromBundle.getVersion() + " ["
                                + fromBundle.getBundleId() + "]");
                return executionControl;
        }
@@ -98,6 +90,25 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
        public static Path getBundleStartupScript(Long bundleId) {
                BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
                Bundle fromBundle = bc.getBundle(bundleId);
+
+               int bundleState = fromBundle.getState();
+               if (Bundle.INSTALLED == bundleState)
+                       throw new IllegalStateException("Bundle " + fromBundle.getSymbolicName() + " is not resolved");
+               if (Bundle.RESOLVED == bundleState) {
+                       try {
+                               fromBundle.start();
+                       } catch (BundleException e) {
+                               throw new IllegalStateException("Cannot start bundle " + fromBundle.getSymbolicName(), e);
+                       }
+                       while (Bundle.ACTIVE != fromBundle.getState())
+                               try {
+                                       Thread.sleep(100);
+                               } catch (InterruptedException e) {
+                                       // we assume the session has been closed
+                                       throw new RuntimeException("Bundle " + fromBundle.getSymbolicName() + " is not active", e);
+                               }
+               }
+
                Path bundleStartupScript = fromBundle.getDataFile("BUNDLE.jsh").toPath();
 
                BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
@@ -110,8 +121,13 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                        packagesToImport.add(pkg.getName());
                }
 
-               List<BundleWire> bundleWires = fromBundleWiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
-               for (BundleWire bw : bundleWires) {
+//             List<BundleWire> exportedWires = fromBundleWiring.getProvidedWires(BundleRevision.PACKAGE_NAMESPACE);
+//             for (BundleWire bw : exportedWires) {
+//                     packagesToImport.add(bw.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).toString());
+//             }
+
+               List<BundleWire> importedWires = fromBundleWiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
+               for (BundleWire bw : importedWires) {
                        packagesToImport.add(bw.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).toString());
                }
 
@@ -121,9 +137,13 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                        }
 
                        String std = """
+                                       /open DEFAULT
                                        import jdk.jshell.spi.ExecutionEnv;
+                                       import java.util.function.*;
 
-                                       InputStream STDIN = new Supplier<InputStream>() {
+                                       /** Redirected standard IO. */
+                                       public class Std {
+                                               final static InputStream in = new Supplier<InputStream>() {
 
                                                        @Override
                                                        public InputStream get() {
@@ -131,7 +151,7 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                                                        }
 
                                                }.get();
-                                       PrintStream STDOUT = new Supplier<PrintStream>() {
+                                               final static PrintStream out = new Supplier<PrintStream>() {
 
                                                        @Override
                                                        public PrintStream get() {
@@ -139,7 +159,7 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                                                        }
 
                                                }.get();
-                                       PrintStream STDERR = new Supplier<PrintStream>() {
+                                               final static PrintStream err = new Supplier<PrintStream>() {
 
                                                        @Override
                                                        public PrintStream get() {
@@ -147,7 +167,9 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                                                        }
 
                                                }.get();
-                                                                               """;
+
+                                       }
+                                       """;
                        writer.write(std);
                } catch (IOException e) {
                        throw new RuntimeException("Cannot writer bundle startup script to " + bundleStartupScript, e);
@@ -157,9 +179,9 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
        }
 
        public static String getBundleClasspath(Long bundleId) throws IOException {
-               String framework = System.getProperty("osgi.framework");
-               Path frameworkLocation = Paths.get(URI.create(framework)).toAbsolutePath();
                BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
+               String framework = bc.getProperty("osgi.framework");
+               Path frameworkLocation = Paths.get(URI.create(framework)).toAbsolutePath();
                Bundle fromBundle = bc.getBundle(bundleId);
 
                BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
@@ -181,7 +203,8 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                                continue bundles;
                        }
                        Path p = bundleToPath(frameworkLocation, b);
-                       classpath.add(p.toString());
+                       if (p != null)
+                               classpath.add(p.toString());
                }
 
                return classpath.toString();
@@ -191,11 +214,17 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                String location = bundle.getLocation();
                if (location.startsWith("initial@reference:file:")) {
                        location = location.substring("initial@reference:file:".length());
-                       Path p = frameworkLocation.getParent().resolve(location).toRealPath();
-                       // TODO load dev.properties from OSGi configuration directory
-                       if (Files.isDirectory(p))
-                               p = p.resolve("bin");
-                       return p;
+                       Path p = frameworkLocation.getParent().resolve(location).toAbsolutePath();
+                       if (Files.exists(p)) {
+                               p = p.toRealPath();
+                               // TODO load dev.properties from OSGi configuration directory
+                               if (Files.isDirectory(p))
+                                       p = p.resolve("bin");
+                               return p;
+                       } else {
+                               log.warn("Ignore bundle " + p + " as it does not exist");
+                               return null;
+                       }
                }
                Path p = Paths.get(location);
                return p;