From 7bc9309f52f7112a7ba0fb86ff37235348f0f08d Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 7 May 2024 19:55:08 +0200 Subject: [PATCH] Improve nested OSGi runtimes --- .../argeo/cms/acr/CmsContentNamespace.java | 25 ++- .../src/org/argeo/api/init/InitConstants.java | 23 ++- .../org/argeo/api/init/RuntimeManager.java | 35 +++- .../src/org/argeo/api/init/defaults.ini | 16 ++ .../org/argeo/init/RuntimeManagerMain.java | 32 ++-- .../init/osgi/ForeignBundleClassLoader.java | 45 +++++ .../osgi/ForeignBundleConnectContent.java | 106 ++++++++++ .../init/osgi/ForeignModuleConnector.java | 86 +++++++++ .../src/org/argeo/init/osgi/OsgiBoot.java | 2 +- .../argeo/init/osgi/OsgiRuntimeContext.java | 41 +++- .../argeo/init/osgi/OsgiRuntimeManager.java | 81 ++++++-- .../init/osgi/SubFrameworkActivator.java | 181 +----------------- 12 files changed, 430 insertions(+), 243 deletions(-) create mode 100644 org.argeo.init/src/org/argeo/api/init/defaults.ini create mode 100644 org.argeo.init/src/org/argeo/init/osgi/ForeignBundleClassLoader.java create mode 100644 org.argeo.init/src/org/argeo/init/osgi/ForeignBundleConnectContent.java create mode 100644 org.argeo.init/src/org/argeo/init/osgi/ForeignModuleConnector.java 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/src/org/argeo/api/init/InitConstants.java b/org.argeo.init/src/org/argeo/api/init/InitConstants.java index 3c558f898..188b3c253 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,30 @@ 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"; // 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 302237f5e..17c330c60 100644 --- a/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java +++ b/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java @@ -16,29 +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); } - startRuntime(relPath, (config) -> { - for (Object key : properties.keySet()) { - config.put(key.toString(), properties.getProperty(key.toString())); - } - }); + 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); @@ -63,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 41102243c..1086be6d8 100644 --- a/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java +++ b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java @@ -21,26 +21,30 @@ 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 Map configuration = new HashMap<>(); - RuntimeManagerMain(Path configArea, Path stateArea) { - RuntimeManager.loadConfig(configArea, configuration); + 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"); +// 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"); configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, - stateArea.resolve(RuntimeManager.STATE).toUri().toString()); + 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(RuntimeManager.DATA).toUri().toString()); + writableArea.resolve(RuntimeManager.DATA).toUri().toString()); logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration); @@ -88,19 +92,17 @@ public class RuntimeManagerMain { 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/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/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 2e8c1042c..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,15 +4,19 @@ 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; @@ -28,20 +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 ConnectFrameworkFactory frameworkFactory; + 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(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; + } } /** @@ -49,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); } @@ -63,7 +92,7 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { // if (opt.isEmpty()) // throw new IllegalStateException("Cannot find OSGi framework"); // framework = opt.get().newFramework(config); - framework = frameworkFactory.newFramework(config, null); + framework = frameworkFactory.newFramework(config, moduleConnector); } try { @@ -101,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 diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java index b8ee1cd2c..097ca44ea 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeManager.java @@ -3,11 +3,16 @@ 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; @@ -28,30 +33,35 @@ class OsgiRuntimeManager implements RuntimeManager { private final static long RUNTIME_SHUTDOWN_TIMEOUT = 60 * 1000; +// private Path ownConfigArea; + private Path baseConfigArea; private Path baseWritableArea; - private Map configuration = new HashMap<>(); - - private Map runtimeContexts = new TreeMap<>(); +// private Map configuration = new HashMap<>(); private ConnectFrameworkFactory frameworkFactory; + private final BundleContext bundleContext; + + private Map runtimeContexts = new TreeMap<>(); + 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))) - .getParent(); + .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() - .getParent(); + .get(URI.create(bundleContext.getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA))).getParent(); - logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration); +// logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration); // System.out.println("java.library.path=" + System.getProperty("java.library.path")); } protected void shutdown() { - // shutdowm runtimes + // shutdown runtimes Map shutdowning = new HashMap<>(runtimeContexts); for (String id : new HashSet<>(runtimeContexts.keySet())) { logger.log(Logger.Level.DEBUG, "Shutting down runtime " + id + " ..."); @@ -80,11 +90,48 @@ class OsgiRuntimeManager implements RuntimeManager { OsgiRuntimeContext loadRuntime(String relPath, Consumer> configCallback) { closeRuntime(relPath, false); + + BundleContext 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); + Path configArea = baseConfigArea.resolve(relPath).getParent().resolve(SHARED); Map config = new HashMap<>(); - RuntimeManager.loadConfig(configArea, config); +// 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); @@ -94,13 +141,11 @@ class OsgiRuntimeManager implements RuntimeManager { config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, writableArea.resolve(DATA).toUri().toString()); // create framework -// Framework framework = frameworkFactory.newFramework(config, null); -// try { -// framework.start(); -// } catch (BundleException e) { -// throw new IllegalStateException("Cannot initialise framework", e); -// } - OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(frameworkFactory, config); + String exportCategories = bundleContext.getProperty(InitConstants.PROP_ARGEO_OSGI_EXPORT_CATEGORIES); + List foreignCategories = exportCategories == null ? new ArrayList<>() + : Arrays.asList(exportCategories.trim().split(",")); + OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(frameworkFactory, config, foreignBundleContext, + foreignCategories); runtimeContexts.put(relPath, runtimeContext); return runtimeContext; } 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 17d6d500f..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()); @@ -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 { - - } - - } } -- 2.39.5