Introduce OSGi sub framework with shared class loaders
[lgpl/argeo-commons.git] / org.argeo.init / src / org / argeo / init / osgi / SubFrameworkActivator.java
diff --git a/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java b/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java
new file mode 100644 (file)
index 0000000..3d6469d
--- /dev/null
@@ -0,0 +1,295 @@
+package org.argeo.init.osgi;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.argeo.api.init.InitConstants;
+import org.argeo.api.init.RuntimeManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleReference;
+import org.osgi.framework.connect.ConnectContent;
+import org.osgi.framework.connect.ConnectFrameworkFactory;
+import org.osgi.framework.connect.ConnectModule;
+import org.osgi.framework.connect.ModuleConnector;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.wiring.BundleWiring;
+
+public class SubFrameworkActivator implements BundleActivator {
+//     private final static String EQUINOX_FRAMEWORK_CLASS = "org.eclipse.osgi.launch.Equinox";
+       private final static String EQUINOX_FRAMEWORK_FACTORY_CLASS = "org.eclipse.osgi.launch.EquinoxFactory";
+
+//     private ClassLoader bundleClassLoader;
+//     private ClassLoader subFrameworkClassLoader;
+       private BundleContext bundleContext;
+
+       private ConnectFrameworkFactory frameworkFactory;
+
+       @Override
+       public void start(BundleContext context) throws Exception {
+               this.bundleContext = context;
+
+               try {
+                       Bundle bundle = context.getBundle();
+                       ClassLoader bundleClassLoader = bundle.adapt(BundleWiring.class).getClassLoader();
+//                     subFrameworkClassLoader = new URLClassLoader(new URL[0], bundleClassLoader);
+
+                       @SuppressWarnings("unchecked")
+                       Class<? extends ConnectFrameworkFactory> frameworkFactoryClass = (Class<? extends ConnectFrameworkFactory>) bundleClassLoader
+                                       .loadClass(EQUINOX_FRAMEWORK_FACTORY_CLASS);
+                       frameworkFactory = frameworkFactoryClass.getConstructor().newInstance();
+
+                       new Thread() {
+
+                               @Override
+                               public void run() {
+                                       for (int i = 0; i < 5; i++) {
+                                               Map<String, String> config = new HashMap<>();
+                                               Path basePase = Paths.get(System.getProperty("user.home"), ".config/argeo/test/", "test" + i);
+                                               config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA,
+                                                               basePase.resolve(RuntimeManager.STATE).toString());
+                                               config.put(InitConstants.PROP_OSGI_INSTANCE_AREA,
+                                                               basePase.resolve(RuntimeManager.DATA).toString());
+                                               config.put("argeo.host", "host" + i);
+                                               config.put("osgi.console", "host" + i + ":2023");
+                                               startFramework(config);
+                                       }
+                               }
+
+                       }.start();
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw e;
+               }
+       }
+
+       Framework startFramework(Map<String, String> config) {
+               try {
+                       URL bundleConfigUrl = bundleContext.getBundle().getEntry("config.ini");
+                       try (InputStream in = bundleConfigUrl.openStream()) {
+                               RuntimeManager.loadConfig(in, config);
+                       }
+
+                       // Equinox
+//                     config.put("osgi.frameworkParentClassloader", "current");
+//                     config.put("osgi.parentClassLoader", "app");
+//                     config.put("osgi.contextClassLoaderParent", "app");
+
+                       ModuleConnector moduleConnector = new ParentBundleModuleConnector(bundleContext);
+
+//                     URL frameworkUrl = URI.create(bundleContext.getProperty("osgi.framework")).toURL();
+//                     URLClassLoader frameworkClassLoader = new URLClassLoader(new URL[] { frameworkUrl, });
+//                     Class<? extends Framework> frameworkClass = (Class<? extends Framework>) frameworkClassLoader
+//                                     .loadClass(EQUINOX_FRAMEWORK_CLASS);
+//                     Framework framework = frameworkClass.getConstructor(Map.class, ModuleConnector.class).newInstance(config,
+//                                     moduleConnector);
+
+                       Framework framework = frameworkFactory.newFramework(config, moduleConnector);
+
+                       framework.init();
+
+                       for (Bundle b : bundleContext.getBundles()) {
+                               if (b.getBundleId() == 0)
+                                       continue;
+                               String location = b.getLocation();
+                               if (location.contains("/org.argeo.tp/") //
+                                               || location.contains("/org.argeo.tp.sys/") //
+                                               || location.contains("/org.argeo.tp.httpd/") //
+                                               || location.contains("/org.argeo.tp.sshd/") //
+                                               || location.contains("/org.argeo.cms/org.argeo.init") //
+                               ) {
+                                       framework.getBundleContext().installBundle(b.getLocation());
+                               }
+                       }
+
+                       OsgiBoot osgiBoot = new OsgiBoot(framework.getBundleContext());
+                       osgiBoot.install();
+//                     OsgiBoot.uninstallBundles(osgiBoot.getBundleContext(), "org.argeo.api.cms");
+//                     OsgiBoot.uninstallBundles(osgiBoot.getBundleContext(), "org.osgi.service.useradmin");
+//                     osgiBoot.getBundleContext()
+//                                     .installBundle("initial@reference:file:../../../../../argeo-commons/org.argeo.api.cms/");
+//                     osgiBoot.getBundleContext().installBundle(
+//                                     "reference:file:/usr/local/share/a2/osgi/equinox/org.argeo.tp.osgi/org.osgi.service.useradmin.1.1.jar");
+                       osgiBoot.refresh();
+                       framework.start();
+                       osgiBoot.startBundles();
+
+//                     for (Bundle b : framework.getBundleContext().getBundles()) {
+//                             BundleContext bc = b.getBundleContext();
+//                             if (bc == null)
+//                                     System.err.println(b.getSymbolicName() + " BC null");
+//                     }
+                       return framework;
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot start framework", e);
+               }
+       }
+
+       @Override
+       public void stop(BundleContext context) throws Exception {
+               bundleContext = null;
+               frameworkFactory = null;
+       }
+
+       class ParentBundleModuleConnector implements ModuleConnector {
+               private final BundleContext foreignBundleContext;
+               private BundleContext localBundleContext;
+
+               public ParentBundleModuleConnector(BundleContext foreignBundleContext) {
+                       this.foreignBundleContext = foreignBundleContext;
+               }
+
+               @Override
+               public Optional<BundleActivator> newBundleActivator() {
+                       return Optional.of(new BundleActivator() {
+                               @Override
+                               public void start(BundleContext context) throws Exception {
+                                       ParentBundleModuleConnector.this.localBundleContext = context;
+                               }
+
+                               @Override
+                               public void stop(BundleContext context) throws Exception {
+                                       ParentBundleModuleConnector.this.localBundleContext = null;
+                               }
+
+                       });
+               }
+
+               @Override
+               public void initialize(File storage, Map<String, String> configuration) {
+               }
+
+               @Override
+               public Optional<ConnectModule> connect(String location) throws BundleException {
+                       Bundle bundle = foreignBundleContext.getBundle(location);
+                       if (bundle != null && bundle.getBundleId() != 0) {
+                               System.out.println("Foreign Bundle: " + bundle.getSymbolicName() + " " + location);
+                               ConnectModule module = new ConnectModule() {
+
+                                       @Override
+                                       public ConnectContent getContent() throws IOException {
+                                               return new ForeignBundleConnectContent(localBundleContext, bundle);
+                                       }
+                               };
+                               return Optional.of(module);
+                       }
+                       return Optional.empty();
+               }
+       }
+
+       class ForeignBundleClassLoader extends ClassLoader implements BundleReference {
+               private BundleContext localBundleContext;
+               private Bundle foreignBundle;
+
+               public ForeignBundleClassLoader(BundleContext localBundleContext, Bundle foreignBundle) {
+                       super("Foreign bundle " + foreignBundle.toString(), Optional
+                                       .ofNullable(foreignBundle.adapt(BundleWiring.class)).map((bw) -> bw.getClassLoader()).orElse(null));
+                       this.localBundleContext = localBundleContext;
+                       this.foreignBundle = foreignBundle;
+               }
+
+               @Override
+               public Bundle getBundle() {
+                       return localBundleContext.getBundle(foreignBundle.getLocation());
+               }
+       }
+
+       class ForeignBundleConnectContent implements ConnectContent {
+               private final Bundle bundle;
+               private final ClassLoader classLoader;
+
+               public ForeignBundleConnectContent(BundleContext localBundleContext, Bundle bundle) {
+                       this.bundle = bundle;
+                       this.classLoader = new ForeignBundleClassLoader(localBundleContext, bundle);
+               }
+
+               @Override
+               public Optional<Map<String, String>> getHeaders() {
+                       Dictionary<String, String> dict = bundle.getHeaders();
+                       List<String> keys = Collections.list(dict.keys());
+                       Map<String, String> dictCopy = keys.stream().collect(Collectors.toMap(Function.identity(), dict::get));
+                       return Optional.of(dictCopy);
+               }
+
+               @Override
+               public Iterable<String> getEntries() throws IOException {
+                       List<String> lst = Collections.list(bundle.findEntries("", "*", true)).stream().map((u) -> u.getPath())
+                                       .toList();
+                       return lst;
+               }
+
+               @Override
+               public Optional<ConnectEntry> getEntry(String path) {
+                       URL u = bundle.getEntry(path);
+                       if (u == null) {
+                               u = bundle.getEntry("bin/" + path);
+                               // System.err.println(u2);
+                       }
+                       if (u == null) {
+                               if ("plugin.xml".equals(path))
+                                       return Optional.empty();
+                               System.err.println(bundle.getSymbolicName() + " " + path + " not found");
+                               return Optional.empty();
+                       }
+                       URL url = u;
+                       ConnectEntry urlConnectEntry = new ConnectEntry() {
+
+                               @Override
+                               public String getName() {
+                                       return path;
+                               }
+
+                               @Override
+                               public long getLastModified() {
+                                       // FIXME
+                                       return System.currentTimeMillis();
+                               }
+
+                               @Override
+                               public InputStream getInputStream() throws IOException {
+                                       return url.openStream();
+                               }
+
+                               @Override
+                               public long getContentLength() {
+                                       return -1;
+                               }
+                       };
+                       return Optional.of(urlConnectEntry);
+               }
+
+               @Override
+               public Optional<ClassLoader> getClassLoader() {
+                       ClassLoader cl;
+                       // cl = bundle.adapt(BundleWiring.class).getClassLoader();
+
+                       // cl = subFrameworkClassLoader;
+                       cl = classLoader;
+                       return Optional.of(cl);
+//                     return Optional.empty();
+               }
+
+               @Override
+               public void open() throws IOException {
+               }
+
+               @Override
+               public void close() throws IOException {
+               }
+
+       }
+}