public void closeRuntime(String relPath, boolean async);
+ /**
+ * Load configs recursively starting with the parent directories, until a
+ * jvm.args file is found.
+ */
static void loadConfig(Path dir, Map<String, String> config) {
- try {
- // System.out.println("Load from " + dir);
- Path jvmArgsPath = dir.resolve(RuntimeManager.JVM_ARGS);
- if (!Files.exists(jvmArgsPath)) {
- // load from parent directory
- loadConfig(dir.getParent(), config);
- }
-
- if (Files.exists(dir))
- for (Path p : Files.newDirectoryStream(dir, "*.ini")) {
- Properties props = new Properties();
- try (InputStream in = Files.newInputStream(p)) {
- props.load(in);
- }
- for (Object key : props.keySet()) {
- config.put(key.toString(), props.getProperty(key.toString()));
- }
+ try {
+ Path jvmArgsPath = dir.resolve(RuntimeManager.JVM_ARGS);
+ if (!Files.exists(jvmArgsPath)) {
+ // load from parent directory
+ loadConfig(dir.getParent(), config);
+ }
+
+ if (Files.exists(dir))
+ for (Path p : Files.newDirectoryStream(dir, "*.ini")) {
+ try (InputStream in = Files.newInputStream(p)) {
+ loadConfig(in, config);
}
- } catch (IOException e) {
- throw new UncheckedIOException("Cannot load configuration from " + dir, e);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException("Cannot load configuration from " + dir, e);
+ }
+ }
+
+ /**
+ * Load config from a {@link Properties} formatted stream. If a property value
+ * starts with a '+' character, itis expected that the last character is a
+ * separator and it will be prepended to the existing value.
+ */
+ static void loadConfig(InputStream in, Map<String, String> config) throws IOException {
+ Properties props = new Properties();
+ props.load(in);
+ for (Object k : props.keySet()) {
+ String key = k.toString();
+ String value = props.getProperty(key);
+ if (value.length() > 1 && '+' == value.charAt(0)) {
+ String currentValue = config.get(key);
+ if (currentValue == null || "".equals(currentValue)) {
+ // remove the + and the trailing separator
+ value = value.substring(1, value.length() - 1);
+ config.put(key, value);
+ } else {
+ // remove the + but keep the trailing separator
+ value = value.substring(1);
+ config.put(key, value + currentValue);
+ }
+ } else {
+ config.put(key, value);
}
}
+ }
}
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.Bundle;
import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.launch.Framework;
BundleContext bc = managerRuntimeContext.getFramework().getBundleContext();
// uninstall init as a bundle since it will be available via OSGi system
- for (Bundle b : bc.getBundles()) {
- if (b.getSymbolicName().equals(SYMBOLIC_NAME_INIT)) {
- b.uninstall();
- }
- }
+ OsgiBoot.uninstallBundles(bc, SYMBOLIC_NAME_INIT);
bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration));
logger.log(Level.DEBUG, "Registered runtime manager");
managerRuntimeContext.waitForStop(0);
- } catch (InterruptedException | BundleException e) {
+ } catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.SortedMap;
* with {@link BundleContext#getProperty(String)}. If these properties are
* <code>null</code>, system properties are used instead.
*/
- public void bootstrap(Map<String, String> properties) {
+ public void bootstrap() {
try {
long begin = System.currentTimeMillis();
-
- // notify start
- String osgiInstancePath = getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA);
- String osgiConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA);
- String osgiSharedConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA);
- logger.log(DEBUG, () -> "OSGi bootstrap starting" //
- + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") //
- + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") //
- + (osgiSharedConfigurationPath != null ? " config: " + osgiSharedConfigurationPath + "" : "") //
- );
-
- // legacy install bundles
- installUrls(getBundlesUrls());
- installUrls(getDistributionUrls());
-
- // A2 install bundles
- provisioningManager.install(null);
-
+ // Install bundles
+ install();
// Make sure fragments are properly considered by refreshing
- refreshFramework();
-
- // start bundles
-// if (properties != null && !Boolean.parseBoolean(properties.get(PROP_OSGI_USE_SYSTEM_PROPERTIES)))
- startBundles(properties);
-// else
-// startBundles();
-
- // complete
+ refresh();
+ // Start bundles
+ startBundles();
long duration = System.currentTimeMillis() - begin;
logger.log(DEBUG, () -> "OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s ("
+ duration + "ms), " + bundleContext.getBundles().length + " bundles");
System.out.println();
}
- /**
- * Calls {@link #bootstrap(Map)} with <code>null</code>.
- *
- * @see #bootstrap(Map)
- */
- @Deprecated
- public void bootstrap() {
- bootstrap(null);
+ 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);
+ logger.log(DEBUG, () -> "OSGi bootstrap starting" //
+ + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") //
+ + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") //
+ + (osgiSharedConfigurationPath != null ? " config: " + osgiSharedConfigurationPath + "" : "") //
+ );
+
+ // legacy install bundles
+ installUrls(getBundlesUrls());
+ installUrls(getDistributionUrls());
+
+ // A2 install bundles
+ provisioningManager.install(null);
+
}
public void update() {
*
* @see OsgiBoot#doStartBundles(Map)
*/
- public void startBundles(Map<String, String> properties) {
+ public void startBundles() {
Map<String, String> map = new TreeMap<>();
// first use properties
- if (properties != null) {
- for (String key : properties.keySet()) {
- String property = key;
- if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
- map.put(property, properties.get(property));
- }
- }
- }
+// if (properties != null) {
+// for (String key : properties.keySet()) {
+// String property = key;
+// if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
+// map.put(property, properties.get(property));
+// }
+// }
+// }
// then try all start level until a maximum
int maxStartLevel = Integer
.parseInt(getProperty(InitConstants.PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL));
}
// finally, override with system properties
- for (Object key : System.getProperties().keySet()) {
- if (key.toString().startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
- map.put(key.toString(), System.getProperty(key.toString()));
- }
- }
+// for (Object key : System.getProperties().keySet()) {
+// if (key.toString().startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
+// map.put(key.toString(), System.getProperty(key.toString()));
+// }
+// }
// start
doStartBundles(map);
}
- void startBundles(Properties properties) {
- Map<String, String> map = new TreeMap<>();
- // first use properties
- if (properties != null) {
- for (Object key : properties.keySet()) {
- String property = key.toString();
- if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
- map.put(property, properties.get(property).toString());
- }
- }
- }
- startBundles(map);
- }
+// void startBundles(Properties properties) {
+// Map<String, String> map = new TreeMap<>();
+// // first use properties
+// if (properties != null) {
+// for (Object key : properties.keySet()) {
+// String property = key.toString();
+// if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
+// map.put(property, properties.get(property).toString());
+// }
+// }
+// }
+// startBundles(map);
+// }
/**
* Start bundle based on keys starting with
return (basePath + '/' + relativePath).replace('/', File.separatorChar);
}
- private void refreshFramework() {
+ public void refresh() {
Bundle systemBundle = bundleContext.getBundle(0);
FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
// TODO deal with refresh breaking native loading (e.g SWT)
return getProperty(name, null);
}
+ /*
+ * BEAN METHODS
+ */
+
+ public BundleContext getBundleContext() {
+ return bundleContext;
+ }
+
/*
* PLAIN OSGI LAUNCHER
*/
}
/*
- * BEAN METHODS
+ * OSGI UTILITIES
*/
+ /** Uninstall all bundles with these symbolic names */
+ public static void uninstallBundles(BundleContext bc, String... symbolicNames) {
+ List<String> lst = Arrays.asList(symbolicNames);
+ for (Bundle b : bc.getBundles()) {
+ String sn = b.getSymbolicName();
+ if (sn == null)
+ continue;
+ if (lst.contains(sn)) {
+ try {
+ b.uninstall();
+ } catch (BundleException e) {
+ logger.log(ERROR, "Cannot uninstall " + sn, e);
+ }
+ }
+ }
- public BundleContext getBundleContext() {
- return bundleContext;
}
-
}
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
}
public Framework launch() {
+ configuration.putAll(startLevelsToProperties());
// start OSGi
framework = OsgiBoot.defaultOsgiLaunch(configuration);
}
}
// start bundles
- osgiBoot.startBundles(startLevelsToProperties());
+ osgiBoot.startBundles();
// if (OsgiBootUtils.isDebug())
// for (Bundle bundle : bc.getBundles()) {
//
// UTILITIES
//
- private Properties startLevelsToProperties() {
- Properties properties = new Properties();
+ private Map<String, String> startLevelsToProperties() {
+ Map<String, String> properties = new HashMap<>();
for (Integer startLevel : startLevels.keySet()) {
String property = InitConstants.PROP_ARGEO_OSGI_START + "." + startLevel;
StringBuilder value = new StringBuilder();
Thread osgiBootThread = new Thread("OSGi boot framework " + frameworkUuuid) {
@Override
public void run() {
- osgiBoot.bootstrap(config);
+ osgiBoot.bootstrap();
}
};
osgiBootThread.start();
--- /dev/null
+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 {
+ }
+
+ }
+}