From: Mathieu Baudier Date: Sat, 11 May 2024 09:45:54 +0000 (+0200) Subject: Make using foreign runtime configurable X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=refs%2Fheads%2Funstable;hp=ee08ba618185ce9be36beb145f509bb8c8bf4eda;p=lgpl%2Fargeo-commons.git Make using foreign runtime configurable --- diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java index 02abd4cda..62d57b689 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java @@ -1,9 +1,6 @@ package org.argeo.cms.acr; -import static java.lang.System.Logger.Level.ERROR; - import java.net.MalformedURLException; -import java.net.URI; import java.net.URL; import java.util.Objects; @@ -55,17 +52,17 @@ public enum CmsContentNamespace implements ContentNamespace { Objects.requireNonNull(namespace); this.namespace = namespace; if (resourceFileName != null) { - // resource = getClass().getResource(RESOURCE_BASE + resourceFileName); - try { - // FIXME workaround when in nested OSGi frameworks - resource = URI.create("platform:/plugin/org.argeo.cms" + RESOURCE_BASE + resourceFileName).toURL(); - } catch (MalformedURLException e) { - resource = null; - System.getLogger(CmsContentNamespace.class.getName()).log(ERROR, - "Cannot load " + resourceFileName + ": " + e.getMessage()); - // throw new IllegalArgumentException("Cannot convert " + resourceFileName + " - // to URL"); - } + resource = getClass().getResource(RESOURCE_BASE + resourceFileName); +// try { +// // FIXME workaround when in nested OSGi frameworks +// resource = URI.create("platform:/plugin/org.argeo.cms" + RESOURCE_BASE + resourceFileName).toURL(); +// } catch (MalformedURLException e) { +// resource = null; +// System.getLogger(CmsContentNamespace.class.getName()).log(ERROR, +// "Cannot load " + resourceFileName + ": " + e.getMessage()); +// // throw new IllegalArgumentException("Cannot convert " + resourceFileName + " +// // to URL"); +// } // Objects.requireNonNull(resource); } if (publicUrl != null) diff --git a/org.argeo.init/bnd.bnd b/org.argeo.init/bnd.bnd index c207cb8f1..5a44c0da0 100644 --- a/org.argeo.init/bnd.bnd +++ b/org.argeo.init/bnd.bnd @@ -1,7 +1,7 @@ Main-Class: org.argeo.init.Service Class-Path: org.eclipse.osgi.jar -Bundle-Activator: org.argeo.init.osgi.Activator +Bundle-Activator: org.argeo.init.osgi.InitActivator Import-Package: \ org.osgi.*;version=0.0.0,\ diff --git a/org.argeo.init/src/org/argeo/api/init/InitConstants.java b/org.argeo.init/src/org/argeo/api/init/InitConstants.java index 3c558f898..864749f60 100644 --- a/org.argeo.init/src/org/argeo/api/init/InitConstants.java +++ b/org.argeo.init/src/org/argeo/api/init/InitConstants.java @@ -5,17 +5,31 @@ public interface InitConstants { String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources"; String PROP_ARGEO_OSGI_START = "argeo.osgi.start"; + + String PROP_OSGI_USE_SYSTEM_PROPERTIES = "osgi.framework.useSystemProperties"; + String PROP_OSGI_INSTANCE_AREA = "osgi.instance.area"; String PROP_OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; String PROP_OSGI_SHARED_CONFIGURATION_AREA = "osgi.sharedConfiguration.area"; + String PROP_OSGI_SHARED_CONFIGURATION_AREA_RO = "osgi.sharedConfiguration.area.readOnly"; String PROP_ARGEO_OSGI_MAX_START_LEVEL = "argeo.osgi.maxStartLevel"; - /** UUID of the parent framework. Marks a nested runtime. */ - String PROP_ARGEO_OSGI_PARENT_UUID = "argeo.osgi.parent.uuid"; - - // OSGi standard properties String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel"; String PROP_OSGI_STARTLEVEL = "osgi.startLevel"; - String PROP_OSGI_USE_SYSTEM_PROPERTIES = "osgi.framework.useSystemProperties"; + + // FOREIGN RUNTIME PROPERTIES + /** + * UUID of the parent framework. It is set by the parent runtime and marks a + * nested runtime. + */ + String PROP_ARGEO_OSGI_PARENT_UUID = "argeo.osgi.parent.uuid"; + /** + * The A2 categories to load from the parent. If not specified, nested runtimes + * won't be used. + */ + @Deprecated + String PROP_ARGEO_OSGI_PARENT_CATEGORIES = "argeo.osgi.parent.categories"; + String PROP_ARGEO_OSGI_EXPORT_CATEGORIES = "argeo.osgi.export.categories"; + String PROP_ARGEO_OSGI_EXPORT_ENABLED = "argeo.osgi.export.enabled"; // Symbolic names String SYMBOLIC_NAME_INIT = "org.argeo.init"; diff --git a/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java b/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java index cb8caeda9..17c330c60 100644 --- a/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java +++ b/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java @@ -2,6 +2,8 @@ package org.argeo.api.init; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; @@ -14,15 +16,53 @@ public interface RuntimeManager { String JVM_ARGS = "jvm.args"; String STATE = "state"; String DATA = "data"; + String SHARED = "shared"; public void startRuntime(String relPath, Consumer> configCallback); public void closeRuntime(String relPath, boolean async); + default void startRuntime(String relPath, String props) { + startRuntime(relPath, (config) -> { + loadProperties(config, props); + }); + } + + static void loadProperties(Map config, Properties properties) { + for (Object key : properties.keySet()) { + config.put(key.toString(), properties.getProperty(key.toString())); + } + } + + static void loadProperties(Map config, String props) { + Properties properties = new Properties(); + try (Reader reader = new StringReader(props)) { + properties.load(reader); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties", e); + } + loadProperties(config, properties); + } + + static void loadProperties(Map config, InputStream in) throws IOException { + Properties properties = new Properties(); + properties.load(in); + loadProperties(config, properties); + } + + static void loadDefaults(Map config) { + try (InputStream in = RuntimeManager.class.getResourceAsStream("defaults.ini")) { + loadProperties(config, in); + } catch (IOException e) { + throw new IllegalStateException("Could not load runtime defaults", e); + } + } + /** * Load configs recursively starting with the parent directories, until a * jvm.args file is found. */ + @Deprecated static void loadConfig(Path dir, Map config) { try { Path jvmArgsPath = dir.resolve(RuntimeManager.JVM_ARGS); @@ -47,6 +87,7 @@ public interface RuntimeManager { * starts with a '+' character, itis expected that the last character is a * separator and it will be prepended to the existing value. */ + @Deprecated static void loadConfig(InputStream in, Map config) throws IOException { Properties props = new Properties(); props.load(in); diff --git a/org.argeo.init/src/org/argeo/api/init/defaults.ini b/org.argeo.init/src/org/argeo/api/init/defaults.ini new file mode 100644 index 000000000..ee9fc40c8 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/init/defaults.ini @@ -0,0 +1,16 @@ +# Disable Equinox Jetty autostart +org.eclipse.equinox.http.jetty.autostart=false + +# System packages +org.osgi.framework.system.packages.extra=\ +sun.misc,\ +sun.security.util,\ +sun.security.internal.spec,\ +sun.security.provider,\ +com.sun.net.httpserver,\ +com.sun.jndi.ldap,\ +com.sun.jndi.ldap.sasl,\ +com.sun.jndi.dns,\ +com.sun.security.jgss,\ +com.sun.nio.file,\ +com.sun.nio.sctp diff --git a/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java index f4ed507c0..1086be6d8 100644 --- a/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java +++ b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java @@ -1,56 +1,50 @@ package org.argeo.init; -import static org.argeo.api.init.InitConstants.SYMBOLIC_NAME_INIT; - import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; -import java.util.HashSet; -import java.util.Hashtable; import java.util.Map; -import java.util.TreeMap; -import java.util.function.Consumer; import org.argeo.api.init.InitConstants; -import org.argeo.api.init.RuntimeContext; import org.argeo.api.init.RuntimeManager; import org.argeo.init.logging.ThinLoggerFinder; -import org.argeo.init.osgi.OsgiBoot; import org.argeo.init.osgi.OsgiRuntimeContext; import org.argeo.internal.init.InternalState; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkEvent; -import org.osgi.framework.launch.Framework; /** * Dynamically configures and launches multiple runtimes, coordinated by a main * one. */ -public class RuntimeManagerMain implements RuntimeManager { +public class RuntimeManagerMain { private final static Logger logger = System.getLogger(RuntimeManagerMain.class.getName()); private final static String ENV_STATE_DIRECTORY = "STATE_DIRECTORY"; -// private final static String ENV_CONFIGURATION_DIRECTORY = "CONFIGURATION_DIRECTORY"; + private final static String ENV_CONFIGURATION_DIRECTORY = "CONFIGURATION_DIRECTORY"; // private final static String ENV_CACHE_DIRECTORY = "CACHE_DIRECTORY"; private final static long RUNTIME_SHUTDOWN_TIMEOUT = 60 * 1000; - private Path baseConfigArea; - private Path baseWritableArea; private Map configuration = new HashMap<>(); - private Map runtimeContexts = new TreeMap<>(); + RuntimeManagerMain(Path configArea, Path writableArea) { +// RuntimeManager.loadConfig(configArea, configuration); + + // integration with OSGi runtime; this will be read by the init bundle +// configuration.put(ServiceMain.PROP_ARGEO_INIT_MAIN, "true"); + RuntimeManager.loadDefaults(configuration); + + configuration.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea.toUri().toString()); + configuration.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA_RO, "true"); + configuration.put(InitConstants.PROP_OSGI_USE_SYSTEM_PROPERTIES, "false"); - RuntimeManagerMain(Path configArea, Path stateArea) { - RuntimeManager.loadConfig(configArea, configuration); - configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea.resolve(STATE).toUri().toString()); + configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, + writableArea.resolve(RuntimeManager.STATE).toUri().toString()); // use config area if instance area is not set if (!configuration.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA)) - configuration.put(InitConstants.PROP_OSGI_INSTANCE_AREA, stateArea.resolve(DATA).toUri().toString()); - this.baseConfigArea = configArea.getParent(); - this.baseWritableArea = stateArea.getParent(); + configuration.put(InitConstants.PROP_OSGI_INSTANCE_AREA, + writableArea.resolve(RuntimeManager.DATA).toUri().toString()); logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration); @@ -58,7 +52,8 @@ public class RuntimeManagerMain implements RuntimeManager { } public void run() { - OsgiRuntimeContext managerRuntimeContext = new OsgiRuntimeContext(configuration); + OsgiRuntimeContext managerRuntimeContext = new OsgiRuntimeContext(OsgiRuntimeContext.loadFrameworkFactory(), + configuration); try { managerRuntimeContext.run(); InternalState.setMainRuntimeContext(managerRuntimeContext); @@ -66,10 +61,10 @@ public class RuntimeManagerMain implements RuntimeManager { // shutdown on exit Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown(), "Runtime shutdown")); - BundleContext bc = managerRuntimeContext.getFramework().getBundleContext(); - // uninstall init as a bundle since it will be available via OSGi system - OsgiBoot.uninstallBundles(bc, SYMBOLIC_NAME_INIT); - bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration)); +// BundleContext bc = managerRuntimeContext.getFramework().getBundleContext(); +// // uninstall init as a bundle since it will be available via OSGi system +// OsgiBoot.uninstallBundles(bc, SYMBOLIC_NAME_INIT); +// bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration)); logger.log(Level.DEBUG, "Registered runtime manager"); managerRuntimeContext.waitForStop(0); @@ -81,21 +76,6 @@ public class RuntimeManagerMain implements RuntimeManager { } protected void shutdown() { - // shutdowm runtimes - Map shutdowning = new HashMap<>(runtimeContexts); - for (String id : new HashSet<>(runtimeContexts.keySet())) { - logger.log(Logger.Level.DEBUG, "Shutting down runtime " + id + " ..."); - closeRuntime(id, true); - } - for (String id : shutdowning.keySet()) - try { - RuntimeContext runtimeContext = shutdowning.get(id); - runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); - } catch (InterruptedException e) { - // silent - } catch (Exception e) { - logger.log(Logger.Level.DEBUG, "Cannot wait for " + id + " to shutdown", e); - } // shutdown manager runtime try { InternalState.getMainRuntimeContext().close(); @@ -108,77 +88,21 @@ public class RuntimeManagerMain implements RuntimeManager { } } - OsgiRuntimeContext loadRuntime(String relPath, Consumer> configCallback) { - closeRuntime(relPath, false); - Path writableArea = baseWritableArea.resolve(relPath); - Path configArea = baseConfigArea.resolve(relPath); - Map config = new HashMap<>(); - RuntimeManager.loadConfig(configArea, config); - config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, writableArea.resolve(STATE).toUri().toString()); - - if (configCallback != null) - configCallback.accept(config); - - // use config area if instance area is not set - if (!config.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA)) - config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, writableArea.resolve(DATA).toUri().toString()); - - OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(config); - runtimeContexts.put(relPath, runtimeContext); - return runtimeContext; - } - - public void startRuntime(String relPath, Consumer> configCallback) { - OsgiRuntimeContext runtimeContext = loadRuntime(relPath, configCallback); - runtimeContext.run(); - Framework framework = runtimeContext.getFramework(); - if (framework != null) {// in case the framework has closed very quickly after run - framework.getBundleContext().addFrameworkListener((e) -> { - if (e.getType() >= FrameworkEvent.STOPPED) { - logger.log(Level.DEBUG, "Externally stopped runtime " + relPath + ". Unregistering...", e); - runtimeContexts.remove(relPath); - } - }); - } else { - closeRuntime(relPath, false); - } - } - - public void closeRuntime(String relPath, boolean async) { - if (!runtimeContexts.containsKey(relPath)) - return; - RuntimeContext runtimeContext = runtimeContexts.get(relPath); - try { - runtimeContext.close(); - if (!async) { - runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); - System.gc(); - } - } catch (Exception e) { - logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e); - } finally { - runtimeContexts.remove(relPath); - } - - } - public static void main(String[] args) { ThinLoggerFinder.reloadConfiguration(); logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + ProcessHandle.current().pid()); Map env = System.getenv(); -// for (String envName : new TreeSet<>(env.keySet())) { -// System.out.format("%s=%s%n", envName, env.get(envName)); -// } - if (args.length < 1) - throw new IllegalArgumentException("A relative configuration directory must be specified"); - Path configArea = Paths.get(System.getProperty("user.dir"), args[0]); + +// if (args.length < 1) +// throw new IllegalArgumentException("A relative configuration directory must be specified"); +// Path configArea = Paths.get(System.getProperty("user.dir"), args[0]); // System.out.println("## Start with PID " + ProcessHandle.current().pid()); // System.out.println("user.dir=" + System.getProperty("user.dir")); - Path stateArea = Paths.get(env.get(ENV_STATE_DIRECTORY)); - - RuntimeManagerMain runtimeManager = new RuntimeManagerMain(configArea, stateArea); + Path writableArea = Paths.get(env.get(ENV_STATE_DIRECTORY)); + Path configArea = Paths.get(env.get(ENV_CONFIGURATION_DIRECTORY)); + RuntimeManagerMain runtimeManager = new RuntimeManagerMain(configArea, writableArea); runtimeManager.run(); } diff --git a/org.argeo.init/src/org/argeo/init/ServiceMain.java b/org.argeo.init/src/org/argeo/init/ServiceMain.java index ca8ba3a7f..4dd30a9da 100644 --- a/org.argeo.init/src/org/argeo/init/ServiceMain.java +++ b/org.argeo.init/src/org/argeo/init/ServiceMain.java @@ -27,6 +27,7 @@ public class ServiceMain { final static String FILE_SYSTEM_PROPERTIES = "system.properties"; + @Deprecated public final static String PROP_ARGEO_INIT_MAIN = "argeo.init.main"; // private static RuntimeContext runtimeContext = null; @@ -121,7 +122,8 @@ public class ServiceMain { config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, dataArea); // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true"); - OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config); + OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext( + OsgiRuntimeContext.loadFrameworkFactory(), config); osgiRuntimeContext.run(); InternalState.setMainRuntimeContext(osgiRuntimeContext); for (Runnable run : postStart) { diff --git a/org.argeo.init/src/org/argeo/init/osgi/Activator.java b/org.argeo.init/src/org/argeo/init/osgi/Activator.java deleted file mode 100644 index 057c21786..000000000 --- a/org.argeo.init/src/org/argeo/init/osgi/Activator.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.argeo.init.osgi; - -import java.lang.System.Logger; -import java.lang.System.Logger.Level; -import java.util.Objects; - -import org.argeo.init.ServiceMain; -import org.argeo.init.logging.ThinLoggerFinder; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; - -/** - * An OSGi configurator. See - * http: - * //wiki.eclipse.org/Configurator - */ -public class Activator implements BundleActivator { - static { - // must be called first - ThinLoggerFinder.lazyInit(); - } - private final static Logger logger = System.getLogger(Activator.class.getName()); - - private Long checkpoint = null; - - private boolean argeoInit = false; - /** Not null if we created it ourselves. */ - private OsgiRuntimeContext runtimeContext; - - public void start(final BundleContext bundleContext) throws Exception { - // The OSGi runtime was created by us, and therefore already initialized - argeoInit = Boolean.parseBoolean(bundleContext.getProperty(ServiceMain.PROP_ARGEO_INIT_MAIN)); - if (!argeoInit) { - if (runtimeContext == null) { - runtimeContext = new OsgiRuntimeContext(bundleContext); - logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator"); - } - - // admin thread -// Thread adminThread = new AdminThread(bundleContext); -// adminThread.start(); - - // bootstrap -// OsgiBoot osgiBoot = new OsgiBoot(bundleContext); - if (checkpoint == null) { -// osgiBoot.bootstrap(); - checkpoint = System.currentTimeMillis(); - } else { - runtimeContext.update(); - checkpoint = System.currentTimeMillis(); - } - } - } - - public void stop(BundleContext context) throws Exception { - if (!argeoInit) { - Objects.nonNull(runtimeContext); - runtimeContext.stop(context); - runtimeContext = null; - } - } - -} diff --git a/org.argeo.init/src/org/argeo/init/osgi/ForeignBundleClassLoader.java b/org.argeo.init/src/org/argeo/init/osgi/ForeignBundleClassLoader.java new file mode 100644 index 000000000..545dbd144 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/osgi/ForeignBundleClassLoader.java @@ -0,0 +1,45 @@ +package org.argeo.init.osgi; + +import java.util.Optional; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.wiring.BundleWiring; + +/** + * A {@link ClassLoader} based on a {@link Bundle} from another OSGi runtime. + */ +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 + protected Bundle getBundle() { + return localBundleContext.getBundle(foreignBundle.getLocation()); + } + +// @Override +// public URL getResource(String resName) { +// URL res = super.getResource(resName); +// return res; +// } +// +// @Override +// protected URL findResource(String resName) { +// Bundle localBundle = getBundle(); +// if (localBundle != null) { +// URL res = localBundle.getEntry(resName); +// if (res != null) +// return res; +// } +// return null; +// } + +} diff --git a/org.argeo.init/src/org/argeo/init/osgi/ForeignBundleConnectContent.java b/org.argeo.init/src/org/argeo/init/osgi/ForeignBundleConnectContent.java new file mode 100644 index 000000000..aa1d7801b --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/osgi/ForeignBundleConnectContent.java @@ -0,0 +1,106 @@ +package org.argeo.init.osgi; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collections; +import java.util.Dictionary; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.connect.ConnectContent; + +/** + * A {@link ConnectContent} based on a {@link Bundle} from another OSGi runtime. + */ +class ForeignBundleConnectContent implements ConnectContent { + private final Bundle foreignBundle; + private final ClassLoader classLoader; + + public ForeignBundleConnectContent(BundleContext localBundleContext, Bundle foreignBundle) { + this.foreignBundle = foreignBundle; + this.classLoader = new ForeignBundleClassLoader(localBundleContext, foreignBundle); + } + + @Override + public Optional> getHeaders() { + Dictionary dict = foreignBundle.getHeaders(); + List keys = Collections.list(dict.keys()); + Map dictCopy = keys.stream().collect(Collectors.toMap(Function.identity(), dict::get)); + return Optional.of(dictCopy); + } + + @Override + public Iterable getEntries() throws IOException { + List lst = Collections.list(foreignBundle.findEntries("", "*", true)).stream().map((u) -> u.getPath()) + .toList(); + return lst; + } + + @Override + public Optional getEntry(String path) { + URL u = foreignBundle.getEntry(path); + if (u == null) { + u = foreignBundle.getEntry("bin/" + path); + // System.err.println(u2); + } + if (u == null) { + if ("plugin.xml".equals(path)) + return Optional.empty(); + if (path.startsWith("META-INF/versions/")) + return Optional.empty(); + System.err.println(foreignBundle.getSymbolicName() + " " + path + " not found"); + return Optional.empty(); + } + URL url = u; + ConnectEntry urlConnectEntry = new ConnectEntry() { + + @Override + public String getName() { + return path; + } + + @Override + public long getLastModified() { + return foreignBundle.getLastModified(); + } + + @Override + public InputStream getInputStream() throws IOException { + return url.openStream(); + } + + @Override + public long getContentLength() { + return -1; + } + }; + return Optional.of(urlConnectEntry); + } + + @Override + public Optional 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 { + + } + +} diff --git a/org.argeo.init/src/org/argeo/init/osgi/ForeignModuleConnector.java b/org.argeo.init/src/org/argeo/init/osgi/ForeignModuleConnector.java new file mode 100644 index 000000000..33297a826 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/osgi/ForeignModuleConnector.java @@ -0,0 +1,86 @@ +package org.argeo.init.osgi; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.connect.ConnectContent; +import org.osgi.framework.connect.ConnectModule; +import org.osgi.framework.connect.ModuleConnector; + +/** + * A {@link ModuleConnector} based on another OSGi runtime. + */ +class ForeignModuleConnector implements ModuleConnector { + private final BundleContext foreignBundleContext; + private final List foreignCategories; + + private BundleContext localBundleContext; + + public ForeignModuleConnector(BundleContext foreignBundleContext, List foreignCategories) { + this.foreignBundleContext = foreignBundleContext; + this.foreignCategories = foreignCategories; + } + + @Override + public Optional newBundleActivator() { + return Optional.of(new BundleActivator() { + @Override + public void start(BundleContext context) throws Exception { + ForeignModuleConnector.this.localBundleContext = context; + } + + @Override + public void stop(BundleContext context) throws Exception { + ForeignModuleConnector.this.localBundleContext = null; + } + + }); + } + + @Override + public void initialize(File storage, Map configuration) { + } + + @Override + public Optional connect(String location) throws BundleException { + // hacks + if (location.contains("org.eclipse.rap.rwt.osgi")) + return Optional.empty(); + + String category = categoryFromLocation(location); + if (category == null || !foreignCategories.contains(category)) + return Optional.empty(); + 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(); + } + + protected String categoryFromLocation(String location) { + // deal with Windows (sigh) + String regexp = File.separatorChar == '\\' ? "\\\\" : "/"; + String[] arr = location.split(regexp); + if (arr.length < 2) + return null; + // TODO make it more robust + String category = arr[arr.length - 2]; + return category; + } +} diff --git a/org.argeo.init/src/org/argeo/init/osgi/InitActivator.java b/org.argeo.init/src/org/argeo/init/osgi/InitActivator.java new file mode 100644 index 000000000..ae0388f35 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/osgi/InitActivator.java @@ -0,0 +1,111 @@ +package org.argeo.init.osgi; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +import org.argeo.api.init.RuntimeManager; +import org.argeo.init.logging.ThinLoggerFinder; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.framework.connect.ConnectFrameworkFactory; +import org.osgi.framework.launch.Framework; + +/** + * An OSGi configurator. See + * http: + * //wiki.eclipse.org/Configurator + */ +public class InitActivator implements BundleActivator { + static { + // must be called first + ThinLoggerFinder.lazyInit(); + } + private final static Logger logger = System.getLogger(InitActivator.class.getName()); + + private Long checkpoint = null; + + // TODO use framework factory SR +// @Deprecated +// private boolean argeoInit = false; + /** Not null if we created it ourselves. */ + private OsgiRuntimeContext runtimeContext; + private ServiceRegistration frameworkFactorySr = null; + + private static OsgiRuntimeManager runtimeManager; + + public void start(final BundleContext bundleContext) throws Exception { + ConnectFrameworkFactory frameworkFactory = OsgiRuntimeContext.getFrameworkFactory(bundleContext); + if (frameworkFactory == null) { +// argeoInit = false; + frameworkFactory = newFrameworkFactory(); + frameworkFactorySr = bundleContext.registerService(ConnectFrameworkFactory.class, frameworkFactory, null); + } + + // The OSGi runtime was created by us, and therefore already initialized +// argeoInit = Boolean.parseBoolean(bundleContext.getProperty(ServiceMain.PROP_ARGEO_INIT_MAIN)); + if (!isArgeoInit()) { + if (runtimeContext == null) { + runtimeContext = new OsgiRuntimeContext(bundleContext); + logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator"); + } + + // admin thread +// Thread adminThread = new AdminThread(bundleContext); +// adminThread.start(); + + // bootstrap +// OsgiBoot osgiBoot = new OsgiBoot(bundleContext); + if (checkpoint == null) { +// osgiBoot.bootstrap(); + checkpoint = System.currentTimeMillis(); + } else { + runtimeContext.update(); + checkpoint = System.currentTimeMillis(); + } + } else { + + if (runtimeManager != null) + throw new IllegalArgumentException("Runtime manager is already set"); + runtimeManager = new OsgiRuntimeManager(bundleContext); + } + } + + public void stop(BundleContext context) throws Exception { + if (!isArgeoInit()) { + frameworkFactorySr.unregister(); + Objects.nonNull(runtimeContext); + runtimeContext.stop(context); + runtimeContext = null; + } + runtimeManager = null; + } + + /** Whether it wa sinitialised by an Argeo Init main class. */ + private boolean isArgeoInit() { + return frameworkFactorySr == null; + } + + public static RuntimeManager getRuntimeManager() { + return runtimeManager; + } + + /** + * Workaround to explicitly instantiate an Equinox + * {@link ConnectFrameworkFactory} when running in a pure OSGi runtime. + */ + private ConnectFrameworkFactory newFrameworkFactory() { + final String EQUINOX_FRAMEWORK_FACTORY_CLASS = "org.eclipse.osgi.launch.EquinoxFactory"; + try { + @SuppressWarnings("unchecked") + Class frameworkFactoryClass = (Class) Framework.class + .getClassLoader().loadClass(EQUINOX_FRAMEWORK_FACTORY_CLASS); + return frameworkFactoryClass.getConstructor().newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | NoSuchMethodException | SecurityException e) { + throw new IllegalStateException("Cannot create OSGi framework factory", e); + } + } +} diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java index 963bab4d4..c6c8471b0 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java @@ -172,7 +172,7 @@ public class OsgiBoot { public void install() { String osgiInstancePath = getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA); String osgiConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA); - String osgiSharedConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA); + String osgiSharedConfigurationPath = getProperty(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA); logger.log(DEBUG, () -> "OSGi bootstrap starting" // + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") // + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") // diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java index 2914be38a..3d4b9768c 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java @@ -4,21 +4,26 @@ import java.io.Serializable; import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.lang.System.LoggerFinder; +import java.util.Arrays; import java.util.Collections; import java.util.Hashtable; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; import java.util.concurrent.Flow; import java.util.function.Consumer; import java.util.function.Supplier; +import org.argeo.api.init.InitConstants; import org.argeo.api.init.RuntimeContext; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.connect.ConnectFrameworkFactory; import org.osgi.framework.launch.Framework; -import org.osgi.framework.launch.FrameworkFactory; /** An OSGi runtime context. */ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { @@ -27,18 +32,42 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { private final static long STOP_FOR_UPDATE_TIMEOUT = 60 * 1000; private final static long CLOSE_TIMEOUT = 60 * 1000; - // private final static String SYMBOLIC_NAME_FELIX_SCR = "org.apache.felix.scr"; - + private final ConnectFrameworkFactory frameworkFactory; private Map config; private Framework framework; -// private OsgiBoot osgiBoot; + + // Nested runtimes +// private final BundleContext foreignBundleContext; + // private final List foreignCategories; + + private final ForeignModuleConnector moduleConnector; /** * Constructor to use when the runtime context will create the OSGi * {@link Framework}. */ - public OsgiRuntimeContext(Map config) { + public OsgiRuntimeContext(ConnectFrameworkFactory frameworkFactory, Map config) { + this(frameworkFactory, config, null, null); + } + + /** + * Constructor to use when the runtime context will create the OSGi + * {@link Framework} and will potentially have a parent runtime. + */ + OsgiRuntimeContext(ConnectFrameworkFactory frameworkFactory, Map config, + BundleContext foreignBundleContext, List foreignCategories) { + this.frameworkFactory = frameworkFactory; this.config = config; + if (foreignCategories != null) { +// String parentCategories = config.get(InitConstants.PROP_ARGEO_OSGI_PARENT_CATEGORIES); +// if (parentCategories != null) { +// Objects.requireNonNull(foreignBundleContext, "Foreign bundle context"); +// List foreignCategories = Arrays.asList(parentCategories.trim().split(",")); +// foreignCategories.removeIf((s) -> "".equals(s)); + this.moduleConnector = new ForeignModuleConnector(foreignBundleContext, foreignCategories); + } else { + this.moduleConnector = null; + } } /** @@ -46,6 +75,9 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { * means. */ OsgiRuntimeContext(BundleContext bundleContext) { + this.frameworkFactory = null; + // TODO try nesting within Eclipse PDE + this.moduleConnector = null; start(bundleContext); } @@ -55,16 +87,18 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { throw new IllegalStateException("OSGi framework is already started"); if (framework == null) { - ServiceLoader sl = ServiceLoader.load(FrameworkFactory.class); - Optional opt = sl.findFirst(); - if (opt.isEmpty()) - throw new IllegalStateException("Cannot find OSGi framework"); - framework = opt.get().newFramework(config); +// ServiceLoader sl = ServiceLoader.load(FrameworkFactory.class); +// Optional opt = sl.findFirst(); +// if (opt.isEmpty()) +// throw new IllegalStateException("Cannot find OSGi framework"); +// framework = opt.get().newFramework(config); + framework = frameworkFactory.newFramework(config, moduleConnector); } try { framework.start(); BundleContext bundleContext = framework.getBundleContext(); + bundleContext.registerService(ConnectFrameworkFactory.class, frameworkFactory, null); start(bundleContext); } catch (BundleException e) { throw new IllegalStateException("Cannot start OSGi framework", e); @@ -96,6 +130,8 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { OsgiBoot osgiBoot = new OsgiBoot(bundleContext); String frameworkUuuid = bundleContext.getProperty(Constants.FRAMEWORK_UUID); +// osgiBoot.bootstrap(); + // separate thread in order to improve logging Thread osgiBootThread = new Thread("OSGi boot framework " + frameworkUuuid) { @Override @@ -189,4 +225,23 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { return framework; } + /** + * Load {@link ConnectFrameworkFactory} from Java service loader. This will not + * work within a pure OSGi runtime, so the reference should be passed to child + * runtimes as an OSGi service. + */ + public static ConnectFrameworkFactory loadFrameworkFactory() { + ServiceLoader sl = ServiceLoader.load(ConnectFrameworkFactory.class); + Optional opt = sl.findFirst(); + if (opt.isEmpty()) + throw new IllegalStateException("Cannot find OSGi framework factory"); + return opt.get(); + } + + public static ConnectFrameworkFactory getFrameworkFactory(BundleContext bundleContext) { + ServiceReference sr = bundleContext.getServiceReference(ConnectFrameworkFactory.class); + if (sr == null) + return null; + return bundleContext.getService(sr); + } } diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java new file mode 100644 index 000000000..6b5365757 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java @@ -0,0 +1,197 @@ +package org.argeo.init.osgi; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.Consumer; + +import org.argeo.api.init.InitConstants; +import org.argeo.api.init.RuntimeContext; +import org.argeo.api.init.RuntimeManager; +import org.argeo.internal.init.InternalState; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkEvent; +import org.osgi.framework.connect.ConnectFrameworkFactory; +import org.osgi.framework.launch.Framework; + +/** + * Dynamically configures and launches multiple runtimes. + */ +class OsgiRuntimeManager implements RuntimeManager { + private final static Logger logger = System.getLogger(OsgiRuntimeManager.class.getName()); + + private final static long RUNTIME_SHUTDOWN_TIMEOUT = 60 * 1000; + +// private Path ownConfigArea; + + private Path baseConfigArea; + private Path baseWritableArea; +// private Map configuration = new HashMap<>(); + + private ConnectFrameworkFactory frameworkFactory; + + private final BundleContext bundleContext; + + private Map runtimeContexts = new TreeMap<>(); + + private boolean useForeignRuntime = Boolean + .parseBoolean(System.getProperty(InitConstants.PROP_ARGEO_OSGI_EXPORT_ENABLED, "true")); + + OsgiRuntimeManager(BundleContext bundleContext) { + Objects.requireNonNull(bundleContext); + this.bundleContext = bundleContext; + frameworkFactory = OsgiRuntimeContext.getFrameworkFactory(bundleContext); + this.baseConfigArea = Paths + .get(URI.create(bundleContext.getProperty(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA))); +// this.baseConfigArea = ownConfigArea.getParent(); + this.baseWritableArea = Paths + .get(URI.create(bundleContext.getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA))).getParent(); + +// logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration); + +// System.out.println("java.library.path=" + System.getProperty("java.library.path")); + } + + protected void shutdown() { + // shutdown runtimes + Map shutdowning = new HashMap<>(runtimeContexts); + for (String id : new HashSet<>(runtimeContexts.keySet())) { + logger.log(Logger.Level.DEBUG, "Shutting down runtime " + id + " ..."); + closeRuntime(id, true); + } + for (String id : shutdowning.keySet()) + try { + RuntimeContext runtimeContext = shutdowning.get(id); + runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); + } catch (InterruptedException e) { + // silent + } catch (Exception e) { + logger.log(Logger.Level.DEBUG, "Cannot wait for " + id + " to shutdown", e); + } + // shutdown manager runtime + try { + InternalState.getMainRuntimeContext().close(); + InternalState.getMainRuntimeContext().waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); +// logger.log(Logger.Level.INFO, "Argeo Init stopped with PID " + ProcessHandle.current().pid()); + System.out.flush(); + } catch (Exception e) { + e.printStackTrace(); + Runtime.getRuntime().halt(1); + } + } + + OsgiRuntimeContext loadRuntime(String relPath, Consumer> configCallback) { + closeRuntime(relPath, false); + + BundleContext foreignBundleContext = null; + if (useForeignRuntime) { + foreignBundleContext = bundleContext; + + Path parentRelPath = Paths.get(relPath).getParent(); + if (parentRelPath != null && Files.exists(baseConfigArea.resolve(parentRelPath))) { + if (!runtimeContexts.containsKey(parentRelPath.toString())) { + + String exportCategories = bundleContext + .getProperty(InitConstants.PROP_ARGEO_OSGI_EXPORT_CATEGORIES); + List foreignCategories = exportCategories == null ? new ArrayList<>() + : Arrays.asList(exportCategories.trim().split(",")); + Path writableArea = baseWritableArea.resolve(parentRelPath); + Path configArea = baseConfigArea.resolve(parentRelPath); + Map config = new HashMap<>(); + RuntimeManager.loadDefaults(config); + config.put(InitConstants.PROP_OSGI_USE_SYSTEM_PROPERTIES, "false"); + config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, + writableArea.resolve(STATE).toUri().toString()); + config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea.toUri().toString()); + config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA_RO, "true"); + OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(frameworkFactory, config, + foreignBundleContext, foreignCategories); + runtimeContexts.put(parentRelPath.toString(), runtimeContext); + runtimeContext.run(); + // FIXME properly stage installation + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // silent + } + } + OsgiRuntimeContext parentRuntimeContext = runtimeContexts.get(parentRelPath.toString()); + foreignBundleContext = parentRuntimeContext.getFramework().getBundleContext(); + } + } + Path writableArea = baseWritableArea.resolve(relPath); + Path configArea = baseConfigArea.resolve(relPath).getParent().resolve(SHARED); + Map config = new HashMap<>(); +// RuntimeManager.loadConfig(configArea, config); + RuntimeManager.loadDefaults(config); + config.put(InitConstants.PROP_OSGI_USE_SYSTEM_PROPERTIES, "false"); + config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, writableArea.resolve(STATE).toUri().toString()); + config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea.toUri().toString()); + config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA_RO, "true"); + + if (configCallback != null) + configCallback.accept(config); + + // use config area if instance area is not set + if (!config.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA)) + config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, writableArea.resolve(DATA).toUri().toString()); + + // create framework + OsgiRuntimeContext runtimeContext; + if (useForeignRuntime) { + String exportCategories = bundleContext.getProperty(InitConstants.PROP_ARGEO_OSGI_EXPORT_CATEGORIES); + List foreignCategories = exportCategories == null ? new ArrayList<>() + : Arrays.asList(exportCategories.trim().split(",")); + runtimeContext = new OsgiRuntimeContext(frameworkFactory, config, foreignBundleContext, foreignCategories); + } else { + runtimeContext = new OsgiRuntimeContext(frameworkFactory, config); + } + runtimeContexts.put(relPath, runtimeContext); + return runtimeContext; + } + + public void startRuntime(String relPath, Consumer> configCallback) { + OsgiRuntimeContext runtimeContext = loadRuntime(relPath, configCallback); + runtimeContext.run(); + Framework framework = runtimeContext.getFramework(); + if (framework != null) {// in case the framework has closed very quickly after run + framework.getBundleContext().addFrameworkListener((e) -> { + if (e.getType() >= FrameworkEvent.STOPPED) { + logger.log(Level.DEBUG, "Externally stopped runtime " + relPath + ". Unregistering...", e); + runtimeContexts.remove(relPath); + } + }); + } else { + closeRuntime(relPath, false); + } + } + + public void closeRuntime(String relPath, boolean async) { + if (!runtimeContexts.containsKey(relPath)) + return; + RuntimeContext runtimeContext = runtimeContexts.get(relPath); + try { + runtimeContext.close(); + if (!async) { + runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); + System.gc(); + } + } catch (Exception e) { + logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e); + } finally { + runtimeContexts.remove(relPath); + } + + } +} diff --git a/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java b/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java index 2aa488050..6f342e50e 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java +++ b/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java @@ -2,38 +2,27 @@ package org.argeo.init.osgi; import static java.lang.System.Logger.Level.INFO; -import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.lang.System.Logger; 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.Iterator; -import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.UUID; -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.Constants; import org.osgi.framework.FrameworkEvent; -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 Logger logger = System.getLogger(SubFrameworkActivator.class.getName()); @@ -66,7 +55,7 @@ public class SubFrameworkActivator implements BundleActivator { .getClassLoader().loadClass(EQUINOX_FRAMEWORK_FACTORY_CLASS); frameworkFactory = frameworkFactoryClass.getConstructor().newInstance(); - boolean test = true; + boolean test = false; if (test) new Thread() { @@ -105,7 +94,7 @@ public class SubFrameworkActivator implements BundleActivator { // config.put("osgi.parentClassLoader", "app"); // config.put("osgi.contextClassLoaderParent", "app"); - ModuleConnector moduleConnector = new ParentBundleModuleConnector(foreignBundleContext); + ModuleConnector moduleConnector = new ForeignModuleConnector(foreignBundleContext, null); // URL frameworkUrl = URI.create(bundleContext.getProperty("osgi.framework")).toURL(); // URLClassLoader frameworkClassLoader = new URLClassLoader(new URL[] { frameworkUrl, }); @@ -182,172 +171,4 @@ public class SubFrameworkActivator implements BundleActivator { frameworkFactory = null; } - static class ParentBundleModuleConnector implements ModuleConnector { - private final BundleContext foreignBundleContext; - private BundleContext localBundleContext; - - public ParentBundleModuleConnector(BundleContext foreignBundleContext) { - this.foreignBundleContext = foreignBundleContext; - } - - @Override - public Optional 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 configuration) { - } - - @Override - public Optional 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(); - } - } - - static 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 - protected Bundle getBundle() { - return localBundleContext.getBundle(foreignBundle.getLocation()); - } - -// @Override -// public URL getResource(String resName) { -// URL res = super.getResource(resName); -// return res; -// } -// -// @Override -// protected URL findResource(String resName) { -// Bundle localBundle = getBundle(); -// if (localBundle != null) { -// URL res = localBundle.getEntry(resName); -// if (res != null) -// return res; -// } -// return null; -// } - - } - - static class ForeignBundleConnectContent implements ConnectContent { - private final Bundle foreignBundle; - private final ClassLoader classLoader; - - public ForeignBundleConnectContent(BundleContext localBundleContext, Bundle foreignBundle) { - this.foreignBundle = foreignBundle; - this.classLoader = new ForeignBundleClassLoader(localBundleContext, foreignBundle); - } - - @Override - public Optional> getHeaders() { - Dictionary dict = foreignBundle.getHeaders(); - List keys = Collections.list(dict.keys()); - Map dictCopy = keys.stream().collect(Collectors.toMap(Function.identity(), dict::get)); - return Optional.of(dictCopy); - } - - @Override - public Iterable getEntries() throws IOException { - List lst = Collections.list(foreignBundle.findEntries("", "*", true)).stream() - .map((u) -> u.getPath()).toList(); - return lst; - } - - @Override - public Optional getEntry(String path) { - URL u = foreignBundle.getEntry(path); - if (u == null) { - u = foreignBundle.getEntry("bin/" + path); - // System.err.println(u2); - } - if (u == null) { - if ("plugin.xml".equals(path)) - return Optional.empty(); - if (path.startsWith("META-INF/versions/")) - return Optional.empty(); - System.err.println(foreignBundle.getSymbolicName() + " " + path + " not found"); - return Optional.empty(); - } - URL url = u; - ConnectEntry urlConnectEntry = new ConnectEntry() { - - @Override - public String getName() { - return path; - } - - @Override - public long getLastModified() { - return foreignBundle.getLastModified(); - } - - @Override - public InputStream getInputStream() throws IOException { - return url.openStream(); - } - - @Override - public long getContentLength() { - return -1; - } - }; - return Optional.of(urlConnectEntry); - } - - @Override - public Optional 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 { - - } - - } } diff --git a/sdk/argeo-build b/sdk/argeo-build index ec9a0b67c..c5ce5217b 160000 --- a/sdk/argeo-build +++ b/sdk/argeo-build @@ -1 +1 @@ -Subproject commit ec9a0b67c92105fc42ec14cdbec7ff9464c6cc3c +Subproject commit c5ce5217b5433d7470628c1ccb90141a028d3378 diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java index 6604b4f3e..7bce69f73 100644 --- a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java @@ -55,8 +55,10 @@ public class MiniDesktopManager { public void stop() { dispose(); if (display != null) { - display.dispose(); - display = null; + display.syncExec(() -> { + display.dispose(); + display = null; + }); } } @@ -288,8 +290,11 @@ public class MiniDesktopManager { } public void dispose() { - if (!rootShell.isDisposed()) - rootShell.dispose(); + if (!rootShell.isDisposed()) { + rootShell.getDisplay().syncExec(() -> { + rootShell.dispose(); + }); + } } protected boolean isFullscreen() { diff --git a/swt/rap/org.argeo.swt.specific.rap/bnd.bnd b/swt/rap/org.argeo.swt.specific.rap/bnd.bnd index bcd9b195f..e022057f3 100644 --- a/swt/rap/org.argeo.swt.specific.rap/bnd.bnd +++ b/swt/rap/org.argeo.swt.specific.rap/bnd.bnd @@ -2,4 +2,5 @@ Import-Package: org.eclipse.swt,\ org.eclipse.jface.dialogs,\ org.eclipse.swt.events,\ javax.servlet.http;version="[3,5)",\ +java.awt.*;resolution:=optional,\ * diff --git a/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd b/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd index 23ea12bc1..f8e30ba2a 100644 --- a/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd +++ b/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd @@ -5,6 +5,9 @@ org.eclipse.core.commands;resolution:=optional,\ org.eclipse.swt,\ javax.servlet.http;resolution:=optional,\ javax.servlet;resolution:=optional,\ +java.awt.*;resolution:=optional,\ +javax.swing.*;resolution:=optional,\ +org.eclipse.swt.awt.*;resolution:=optional,\ * Export-Package: org.argeo.*,\