From 722dc6b20b15014e3d963bd09617ddfd9b63a89f Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 5 Mar 2024 10:14:20 +0100 Subject: [PATCH] Refactor Argeo init --- .../src/org/argeo/cms/ssh/CmsSshServer.java | 4 +- .../cms/internal/runtime/CmsContextImpl.java | 3 +- .../cms/internal/runtime/CmsStateImpl.java | 3 +- org.argeo.init/bnd.bnd | 2 + .../org/argeo/{init => api}/a2/A2Branch.java | 2 +- .../argeo/{init => api}/a2/A2Component.java | 2 +- .../{init => api}/a2/A2Contribution.java | 2 +- .../argeo/{init => api}/a2/A2Exception.java | 2 +- .../org/argeo/{init => api}/a2/A2Module.java | 2 +- .../org/argeo/{init => api}/a2/A2Source.java | 2 +- .../a2/AbstractProvisioningSource.java | 2 +- .../{init => api}/a2/ClasspathSource.java | 2 +- .../argeo/{init => api}/a2/FsA2Source.java | 2 +- .../argeo/{init => api}/a2/FsM2Source.java | 2 +- .../argeo/{init => api}/a2/OsgiContext.java | 2 +- .../{init => api}/a2/ProvisioningManager.java | 6 +- .../{init => api}/a2/ProvisioningSource.java | 2 +- .../argeo/{init => api}/a2/package-info.java | 2 +- .../src/org/argeo/api/init/InitConstants.java | 22 ++ .../argeo/{ => api}/init/RuntimeContext.java | 2 +- .../org/argeo/api/init/RuntimeManager.java | 11 + ...meManager.java => RuntimeManagerMain.java} | 89 ++++- .../init/{Service.java => ServiceMain.java} | 51 ++- .../org/argeo/init/StaticRuntimeContext.java | 2 + .../src/org/argeo/init/SysInitMain.java | 308 ++++++++++++++++++ .../org/argeo/init/logging/ThinLogging.java | 44 +-- .../src/org/argeo/init/osgi/Activator.java | 4 +- .../src/org/argeo/init/osgi/OsgiBoot.java | 61 ++-- .../src/org/argeo/init/osgi/OsgiBuilder.java | 9 +- .../argeo/init/osgi/OsgiRuntimeContext.java | 2 +- .../argeo/internal/init/InternalState.java | 21 ++ 31 files changed, 536 insertions(+), 134 deletions(-) rename org.argeo.init/src/org/argeo/{init => api}/a2/A2Branch.java (98%) rename org.argeo.init/src/org/argeo/{init => api}/a2/A2Component.java (98%) rename org.argeo.init/src/org/argeo/{init => api}/a2/A2Contribution.java (99%) rename org.argeo.init/src/org/argeo/{init => api}/a2/A2Exception.java (91%) rename org.argeo.init/src/org/argeo/{init => api}/a2/A2Module.java (97%) rename org.argeo.init/src/org/argeo/{init => api}/a2/A2Source.java (95%) rename org.argeo.init/src/org/argeo/{init => api}/a2/AbstractProvisioningSource.java (99%) rename org.argeo.init/src/org/argeo/{init => api}/a2/ClasspathSource.java (97%) rename org.argeo.init/src/org/argeo/{init => api}/a2/FsA2Source.java (99%) rename org.argeo.init/src/org/argeo/{init => api}/a2/FsM2Source.java (98%) rename org.argeo.init/src/org/argeo/{init => api}/a2/OsgiContext.java (98%) rename org.argeo.init/src/org/argeo/{init => api}/a2/ProvisioningManager.java (98%) rename org.argeo.init/src/org/argeo/{init => api}/a2/ProvisioningSource.java (95%) rename org.argeo.init/src/org/argeo/{init => api}/a2/package-info.java (56%) create mode 100644 org.argeo.init/src/org/argeo/api/init/InitConstants.java rename org.argeo.init/src/org/argeo/{ => api}/init/RuntimeContext.java (90%) create mode 100644 org.argeo.init/src/org/argeo/api/init/RuntimeManager.java rename org.argeo.init/src/org/argeo/init/{RuntimeManager.java => RuntimeManagerMain.java} (59%) rename org.argeo.init/src/org/argeo/init/{Service.java => ServiceMain.java} (75%) create mode 100644 org.argeo.init/src/org/argeo/init/SysInitMain.java create mode 100644 org.argeo.init/src/org/argeo/internal/init/InternalState.java diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java index 98bb04544..2480c3df4 100644 --- a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java @@ -207,9 +207,9 @@ public class CmsSshServer implements CmsSshd { } catch (IOException | KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException | CertificateException | IllegalArgumentException | UnrecoverableKeyException e) { if (log.isTraceEnabled()) - log.error("Cannot add node public key to SSH authorized keys", e); + log.warn("Cannot add node public key to SSH authorized keys", e); else - log.error("Cannot add node public key to SSH authorized keys: " + e.getMessage()); + log.warn("Cannot add node public key to SSH authorized keys: " + e); return null; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java index 01d285b8c..25f8dad6a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java @@ -96,7 +96,8 @@ public class CmsContextImpl implements CmsContext { availableSince = System.currentTimeMillis(); long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s"; - log.info("## ARGEO CMS AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##"); + log.info("## ARGEO CMS " + cmsState.getUuid() + " AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + + " ##"); if (log.isDebugEnabled()) { log.debug("## state: " + state); if (data != null) diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java index 60a51b44f..6fe8dcb7b 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java @@ -259,7 +259,8 @@ public class CmsStateImpl implements CmsState { log.debug("CMS stopping... (" + this.uuid + ")"); long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60; - log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##"); + log.info("## ARGEO CMS " + uuid + " STOPPED after " + (duration / 60) + "h " + (duration % 60) + + "min uptime ##"); } private void firstInit() throws IOException { diff --git a/org.argeo.init/bnd.bnd b/org.argeo.init/bnd.bnd index 3dbe6831b..34023cc7d 100644 --- a/org.argeo.init/bnd.bnd +++ b/org.argeo.init/bnd.bnd @@ -6,3 +6,5 @@ Bundle-Activator: org.argeo.init.osgi.Activator Import-Package: \ org.osgi.*;version=0.0.0,\ java.util.logging;resolution:=optional + +Export-Package: org.argeo.api.* \ No newline at end of file diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Branch.java b/org.argeo.init/src/org/argeo/api/a2/A2Branch.java similarity index 98% rename from org.argeo.init/src/org/argeo/init/a2/A2Branch.java rename to org.argeo.init/src/org/argeo/api/a2/A2Branch.java index cd8d89541..1db82a74a 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Branch.java +++ b/org.argeo.init/src/org/argeo/api/a2/A2Branch.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import java.util.Collections; import java.util.SortedMap; diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Component.java b/org.argeo.init/src/org/argeo/api/a2/A2Component.java similarity index 98% rename from org.argeo.init/src/org/argeo/init/a2/A2Component.java rename to org.argeo.init/src/org/argeo/api/a2/A2Component.java index 894270630..d8b7512bd 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Component.java +++ b/org.argeo.init/src/org/argeo/api/a2/A2Component.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import java.util.Collections; import java.util.SortedMap; diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java b/org.argeo.init/src/org/argeo/api/a2/A2Contribution.java similarity index 99% rename from org.argeo.init/src/org/argeo/init/a2/A2Contribution.java rename to org.argeo.init/src/org/argeo/api/a2/A2Contribution.java index 9de09cecf..f099ceb2b 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java +++ b/org.argeo.init/src/org/argeo/api/a2/A2Contribution.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import java.util.Collections; import java.util.Map; diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Exception.java b/org.argeo.init/src/org/argeo/api/a2/A2Exception.java similarity index 91% rename from org.argeo.init/src/org/argeo/init/a2/A2Exception.java rename to org.argeo.init/src/org/argeo/api/a2/A2Exception.java index 6ba87a7d0..2e609f0f4 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Exception.java +++ b/org.argeo.init/src/org/argeo/api/a2/A2Exception.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; /** Unchecked A2 provisioning exception. */ public class A2Exception extends RuntimeException { diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Module.java b/org.argeo.init/src/org/argeo/api/a2/A2Module.java similarity index 97% rename from org.argeo.init/src/org/argeo/init/a2/A2Module.java rename to org.argeo.init/src/org/argeo/api/a2/A2Module.java index 0b6d3a91c..4ad43625f 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Module.java +++ b/org.argeo.init/src/org/argeo/api/a2/A2Module.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import org.osgi.framework.Version; diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Source.java b/org.argeo.init/src/org/argeo/api/a2/A2Source.java similarity index 95% rename from org.argeo.init/src/org/argeo/init/a2/A2Source.java rename to org.argeo.init/src/org/argeo/api/a2/A2Source.java index 19134b784..5883e9d99 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Source.java +++ b/org.argeo.init/src/org/argeo/api/a2/A2Source.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import java.net.URI; diff --git a/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java b/org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java similarity index 99% rename from org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java rename to org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java index f946add69..62416fe48 100644 --- a/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java +++ b/org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import java.io.FileOutputStream; import java.io.IOException; diff --git a/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java b/org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java similarity index 97% rename from org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java rename to org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java index 12de4228b..2f1cf95fa 100644 --- a/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java +++ b/org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import java.io.File; import java.io.IOException; diff --git a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java b/org.argeo.init/src/org/argeo/api/a2/FsA2Source.java similarity index 99% rename from org.argeo.init/src/org/argeo/init/a2/FsA2Source.java rename to org.argeo.init/src/org/argeo/api/a2/FsA2Source.java index acc553d7d..ebb602049 100644 --- a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java +++ b/org.argeo.init/src/org/argeo/api/a2/FsA2Source.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import java.io.IOException; import java.net.URI; diff --git a/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java b/org.argeo.init/src/org/argeo/api/a2/FsM2Source.java similarity index 98% rename from org.argeo.init/src/org/argeo/init/a2/FsM2Source.java rename to org.argeo.init/src/org/argeo/api/a2/FsM2Source.java index 0313d20f3..f63a90468 100644 --- a/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java +++ b/org.argeo.init/src/org/argeo/api/a2/FsM2Source.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import java.io.File; import java.io.IOException; diff --git a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java b/org.argeo.init/src/org/argeo/api/a2/OsgiContext.java similarity index 98% rename from org.argeo.init/src/org/argeo/init/a2/OsgiContext.java rename to org.argeo.init/src/org/argeo/api/a2/OsgiContext.java index 7f1133f67..dfed170d1 100644 --- a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java +++ b/org.argeo.init/src/org/argeo/api/a2/OsgiContext.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import org.argeo.init.osgi.OsgiBootUtils; import org.osgi.framework.Bundle; diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java similarity index 98% rename from org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java rename to org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java index 80006264c..104f08a5d 100644 --- a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java +++ b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java @@ -1,7 +1,7 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; -import static org.argeo.init.a2.A2Source.SCHEME_A2; -import static org.argeo.init.a2.A2Source.SCHEME_A2_REFERENCE; +import static org.argeo.api.a2.A2Source.SCHEME_A2; +import static org.argeo.api.a2.A2Source.SCHEME_A2_REFERENCE; import java.io.File; import java.io.UnsupportedEncodingException; diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java b/org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java similarity index 95% rename from org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java rename to org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java index 99356300e..ddba2a9e7 100644 --- a/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java +++ b/org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java @@ -1,4 +1,4 @@ -package org.argeo.init.a2; +package org.argeo.api.a2; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; diff --git a/org.argeo.init/src/org/argeo/init/a2/package-info.java b/org.argeo.init/src/org/argeo/api/a2/package-info.java similarity index 56% rename from org.argeo.init/src/org/argeo/init/a2/package-info.java rename to org.argeo.init/src/org/argeo/api/a2/package-info.java index bb8fa6e00..6a8bf711b 100644 --- a/org.argeo.init/src/org/argeo/init/a2/package-info.java +++ b/org.argeo.init/src/org/argeo/api/a2/package-info.java @@ -1,2 +1,2 @@ /** A2 OSGi repository format. */ -package org.argeo.init.a2; \ No newline at end of file +package org.argeo.api.a2; \ No newline at end of file diff --git a/org.argeo.init/src/org/argeo/api/init/InitConstants.java b/org.argeo.init/src/org/argeo/api/init/InitConstants.java new file mode 100644 index 000000000..998a0a4b7 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/init/InitConstants.java @@ -0,0 +1,22 @@ +package org.argeo.api.init; + +/** Supported init constants. */ +public interface InitConstants { + + String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources"; + String PROP_ARGEO_OSGI_START = "argeo.osgi.start"; + 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_ARGEO_OSGI_MAX_START_LEVEL = "argeo.osgi.maxStartLevel"; + + // 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"; + + // Symbolic names + String SYMBOLIC_NAME_INIT = "org.argeo.init"; + String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi"; + +} diff --git a/org.argeo.init/src/org/argeo/init/RuntimeContext.java b/org.argeo.init/src/org/argeo/api/init/RuntimeContext.java similarity index 90% rename from org.argeo.init/src/org/argeo/init/RuntimeContext.java rename to org.argeo.init/src/org/argeo/api/init/RuntimeContext.java index d83f2ca1c..9f78d1316 100644 --- a/org.argeo.init/src/org/argeo/init/RuntimeContext.java +++ b/org.argeo.init/src/org/argeo/api/init/RuntimeContext.java @@ -1,4 +1,4 @@ -package org.argeo.init; +package org.argeo.api.init; /** A runtime context with a life cycle. */ public interface RuntimeContext extends Runnable { diff --git a/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java b/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java new file mode 100644 index 000000000..1f4a3eca4 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java @@ -0,0 +1,11 @@ +package org.argeo.api.init; + +import java.util.Map; +import java.util.function.Consumer; + +/** Dynamically manages multiple runtimes within a single JVM. */ +public interface RuntimeManager { + public void startRuntime(String relPath, Consumer> configCallback); + + public void stopRuntime(String relPath, boolean async); +} diff --git a/org.argeo.init/src/org/argeo/init/RuntimeManager.java b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java similarity index 59% rename from org.argeo.init/src/org/argeo/init/RuntimeManager.java rename to org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java index a5f48d446..72b673b68 100644 --- a/org.argeo.init/src/org/argeo/init/RuntimeManager.java +++ b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java @@ -1,5 +1,7 @@ package org.argeo.init; +import static org.argeo.api.init.InitConstants.SYMBOLIC_NAME_INIT; + import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -9,25 +11,36 @@ import java.nio.file.Files; 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.Properties; 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.Bundle; import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; -public class RuntimeManager { - private final static Logger logger = System.getLogger(RuntimeManager.class.getName()); +/** + * Dynamically configures and launches multiple runtimes, coordinated by a main + * one. + */ +public class RuntimeManagerMain implements RuntimeManager { + 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_CACHE_DIRECTORY = "CACHE_DIRECTORY"; + private final static long RUNTIME_SHUTDOWN_TIMEOUT = 60 * 1000; + private final static String JVM_ARGS = "jvm.args"; private Path baseConfigArea; @@ -36,9 +49,9 @@ public class RuntimeManager { private Map runtimeContexts = new TreeMap<>(); - RuntimeManager(Path configArea, Path stateArea) { + RuntimeManagerMain(Path configArea, Path stateArea) { loadConfig(configArea, configuration); - configuration.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea.toUri().toString()); + configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea.toUri().toString()); this.baseConfigArea = configArea.getParent(); this.baseStateArea = stateArea.getParent(); @@ -47,31 +60,62 @@ public class RuntimeManager { } public void run() { - logger.log(Level.DEBUG, "Start OSGi"); - try (OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(configuration)) { - runtimeContext.run(); + OsgiRuntimeContext managerRuntimeContext = new OsgiRuntimeContext(configuration); + try { + managerRuntimeContext.run(); + InternalState.setMainRuntimeContext(managerRuntimeContext); - BundleContext bc = runtimeContext.getFramework().getBundleContext(); + // 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 for (Bundle b : bc.getBundles()) { - if (b.getSymbolicName().equals("org.argeo.init")) { + if (b.getSymbolicName().equals(SYMBOLIC_NAME_INIT)) { b.uninstall(); } } bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration)); logger.log(Level.DEBUG, "Registered runtime manager"); - runtimeContext.waitForStop(0); - } catch (Exception e) { + managerRuntimeContext.waitForStop(0); + } catch (InterruptedException | BundleException e) { e.printStackTrace(); System.exit(1); } } + 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 + " ..."); + stopRuntime(id, true); + } + for (String id : shutdowning.keySet()) + try { + RuntimeContext runtimeContext = shutdowning.get(id); + runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); + } catch (InterruptedException e) { + // silent + } + + // 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); + } + } + public static void loadConfig(Path dir, Map config) { try { - System.out.println("Load from "+dir); + System.out.println("Load from " + dir); Path jvmArgsPath = dir.resolve(JVM_ARGS); if (!Files.exists(jvmArgsPath)) { // load from parent directory @@ -94,15 +138,20 @@ public class RuntimeManager { } OsgiRuntimeContext loadRuntime(String relPath, Consumer> configCallback) { - stopRuntime(relPath); + stopRuntime(relPath, false); Path stateArea = baseStateArea.resolve(relPath); Path configArea = baseConfigArea.resolve(relPath); Map config = new HashMap<>(); loadConfig(configArea, config); - config.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea.toUri().toString()); + config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea.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, config.get(InitConstants.PROP_OSGI_CONFIGURATION_AREA)); + OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(config); runtimeContexts.put(relPath, runtimeContext); return runtimeContext; @@ -113,13 +162,16 @@ public class RuntimeManager { runtimeContext.run(); } - public void stopRuntime(String relPath) { + public void stopRuntime(String relPath, boolean async) { if (!runtimeContexts.containsKey(relPath)) return; RuntimeContext runtimeContext = runtimeContexts.get(relPath); try { runtimeContext.close(); - runtimeContext.waitForStop(60 * 1000); + if (!async) { + runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); + System.gc(); + } } catch (Exception e) { logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e); } finally { @@ -130,6 +182,7 @@ public class RuntimeManager { public static void main(String[] args) { ThinLoggerFinder.reloadConfiguration(); + logger.log(Logger.Level.INFO, () -> "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)); @@ -143,7 +196,7 @@ public class RuntimeManager { Path stateArea = Paths.get(env.get(ENV_STATE_DIRECTORY)); - RuntimeManager runtimeManager = new RuntimeManager(configArea, stateArea); + RuntimeManagerMain runtimeManager = new RuntimeManagerMain(configArea, stateArea); runtimeManager.run(); } diff --git a/org.argeo.init/src/org/argeo/init/Service.java b/org.argeo.init/src/org/argeo/init/ServiceMain.java similarity index 75% rename from org.argeo.init/src/org/argeo/init/Service.java rename to org.argeo.init/src/org/argeo/init/ServiceMain.java index b080a7513..ca8ba3a7f 100644 --- a/org.argeo.init/src/org/argeo/init/Service.java +++ b/org.argeo.init/src/org/argeo/init/ServiceMain.java @@ -16,23 +16,24 @@ import java.util.Objects; import java.util.Properties; import java.util.TreeMap; +import org.argeo.api.init.InitConstants; import org.argeo.init.logging.ThinLoggerFinder; -import org.argeo.init.osgi.OsgiBoot; import org.argeo.init.osgi.OsgiRuntimeContext; +import org.argeo.internal.init.InternalState; -/** Configure and launch an Argeo service. */ -public class Service { - private final static Logger logger = System.getLogger(Service.class.getName()); +/** Configures and launches a single runtime, typically as a systemd service. */ +public class ServiceMain { + private final static Logger logger = System.getLogger(ServiceMain.class.getName()); final static String FILE_SYSTEM_PROPERTIES = "system.properties"; public final static String PROP_ARGEO_INIT_MAIN = "argeo.init.main"; - private static RuntimeContext runtimeContext = null; +// private static RuntimeContext runtimeContext = null; private static List postStart = Collections.synchronizedList(new ArrayList<>()); - protected Service(String[] args) { + protected ServiceMain(String[] args) { } public static void main(String[] args) { @@ -42,10 +43,9 @@ public class Service { // shutdown on exit Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { - if (Service.runtimeContext != null) { -// System.out.println("Argeo Init stopping with PID " + pid); - Service.runtimeContext.close(); - Service.runtimeContext.waitForStop(0); + if (InternalState.getMainRuntimeContext() != null) { + InternalState.getMainRuntimeContext().close(); + InternalState.getMainRuntimeContext().waitForStop(0); } } catch (Exception e) { e.printStackTrace(); @@ -54,9 +54,9 @@ public class Service { }, "Runtime shutdown")); // TODO use args as well - String dataArea = System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA); - String stateArea = System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA); - String configArea = System.getProperty(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA); + String dataArea = System.getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA); + String stateArea = System.getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA); + String configArea = System.getProperty(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA); if (configArea != null) { Path configAreaPath = Paths.get(configArea); @@ -94,9 +94,9 @@ public class Service { sysprops: for (Object key : new TreeMap<>(System.getProperties()).keySet()) { String keyStr = key.toString(); switch (keyStr) { - case OsgiBoot.PROP_OSGI_CONFIGURATION_AREA: - case OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA: - case OsgiBoot.PROP_OSGI_INSTANCE_AREA: + case InitConstants.PROP_OSGI_CONFIGURATION_AREA: + case InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA: + case InitConstants.PROP_OSGI_INSTANCE_AREA: // we should already have dealt with those continue sysprops; default: @@ -114,16 +114,16 @@ public class Service { try { try { if (stateArea != null) - config.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea); + config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea); if (configArea != null) - config.put(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea); + config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea); if (dataArea != null) - config.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, dataArea); + config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, dataArea); // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true"); OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config); osgiRuntimeContext.run(); - Service.runtimeContext = osgiRuntimeContext; + InternalState.setMainRuntimeContext(osgiRuntimeContext); for (Runnable run : postStart) { try { run.run(); @@ -131,11 +131,11 @@ public class Service { logger.log(Level.ERROR, "Cannot run post start callback " + run, e); } } - Service.runtimeContext.waitForStop(0); + InternalState.getMainRuntimeContext().waitForStop(0); } catch (NoClassDefFoundError noClassDefFoundE) { StaticRuntimeContext staticRuntimeContext = new StaticRuntimeContext((Map) config); staticRuntimeContext.run(); - Service.runtimeContext = staticRuntimeContext; + InternalState.setMainRuntimeContext(staticRuntimeContext); for (Runnable run : postStart) { try { run.run(); @@ -143,7 +143,7 @@ public class Service { logger.log(Level.ERROR, "Cannot run post start callback " + run, e); } } - Service.runtimeContext.waitForStop(0); + InternalState.getMainRuntimeContext().waitForStop(0); } } catch (Exception e) { e.printStackTrace(); @@ -152,11 +152,6 @@ public class Service { logger.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid); } - /** The root runtime context in this JVM. */ - public static RuntimeContext getRuntimeContext() { - return runtimeContext; - } - /** Add a post-start call back to be run after the runtime has been started. */ public static void addPostStart(Runnable runnable) { postStart.add(runnable); diff --git a/org.argeo.init/src/org/argeo/init/StaticRuntimeContext.java b/org.argeo.init/src/org/argeo/init/StaticRuntimeContext.java index e01e6194d..51a968804 100644 --- a/org.argeo.init/src/org/argeo/init/StaticRuntimeContext.java +++ b/org.argeo.init/src/org/argeo/init/StaticRuntimeContext.java @@ -2,6 +2,8 @@ package org.argeo.init; import java.util.Map; +import org.argeo.api.init.RuntimeContext; + public class StaticRuntimeContext implements RuntimeContext { private Map config; diff --git a/org.argeo.init/src/org/argeo/init/SysInitMain.java b/org.argeo.init/src/org/argeo/init/SysInitMain.java new file mode 100644 index 000000000..8e59c24e0 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/SysInitMain.java @@ -0,0 +1,308 @@ +package org.argeo.init; +//#! /usr/bin/java --source 17 @/usr/local/etc/freed/pid1/jvm.args + +import static java.lang.System.Logger.Level.DEBUG; +import static java.lang.System.Logger.Level.ERROR; +import static java.lang.System.Logger.Level.INFO; +import static java.lang.System.Logger.Level.WARNING; + +import java.io.Console; +import java.io.IOException; +import java.lang.System.Logger; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +import sun.misc.Signal; + +/** A minimalistic Linux init process. */ +class SysInitMain { + final static AtomicInteger runLevel = new AtomicInteger(-1); + + private final static Logger logger = System.getLogger(SysInitMain.class.getName()); + + private final static List initDServices = Collections.synchronizedList(new ArrayList<>()); + + public static void main(String... args) { + try { + final long pid = ProcessHandle.current().pid(); + Signal.handle(new Signal("TERM"), (signal) -> { + System.out.println("SIGTERM caught"); + System.exit(0); + }); + Signal.handle(new Signal("INT"), (signal) -> { + System.out.println("SIGINT caught"); + System.exit(0); + }); + Signal.handle(new Signal("HUP"), (signal) -> { + System.out.println("SIGHUP caught"); + System.exit(0); + }); + + boolean isSystemInit = pid == 1 || pid == 2; + + if (isSystemInit && args.length > 0 && ("1".equals(args[0]) // + || "single".equals(args[0]) // + || "emergency".equals(args[0]))) { + runLevel.set(1); + for (Object key : new TreeMap<>(System.getProperties()).keySet()) { + System.out.println(key + "=" + System.getProperty(key.toString())); + } + System.out.println("Single user mode"); + System.out.flush(); + ProcessBuilder pb = new ProcessBuilder("/bin/bash"); + pb.redirectError(ProcessBuilder.Redirect.INHERIT); + pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); + pb.redirectInput(ProcessBuilder.Redirect.INHERIT); + Process singleUserShell = pb.start(); + singleUserShell.waitFor(); + } else { + if (args.length == 0) + runLevel.set(5); + else + runLevel.set(Integer.parseInt(args[0])); + + if (runLevel.get() == 0) {// shutting down the whole system + if (!isSystemInit) { + logger.log(INFO, "Shutting down system..."); + shutdown(false); + System.exit(0); + } else { + logger.log(ERROR, "Cannot start at run level " + runLevel.get()); + System.exit(1); + } + } else if (runLevel.get() == 6) {// reboot the whole system + if (!isSystemInit) { + logger.log(INFO, "Rebooting the system..."); + shutdown(true); + } else { + logger.log(ERROR, "Cannot start at run level " + runLevel.get()); + System.exit(1); + } + } + + logger.log(INFO, "FREEd Init daemon starting with pid " + pid + " after " + + ManagementFactory.getRuntimeMXBean().getUptime() + " ms"); + // hostname + String hostname = Files.readString(Paths.get("/etc/hostname")); + new ProcessBuilder("/usr/bin/hostname", hostname).start(); + logger.log(DEBUG, "Set hostname to " + hostname); + // networking + initSysctl(); + startInitDService("networking", true); +// Thread.sleep(3000);// leave some time for network to start up + if (!waitForNetwork(10 * 1000)) + logger.log(ERROR, "No network available"); + + // OpenSSH + // TODO make it coherent with Java sshd + startInitDService("ssh", true); + + // NSS services + startInitDService("nslcd", false);// Note: nslcd fails to stop + + // login prompt + ServiceMain.addPostStart(() -> new LoginThread().start()); + + // init Argeo CMS + logger.log(INFO, "FREEd Init daemon starting Argeo Init after " + + ManagementFactory.getRuntimeMXBean().getUptime() + " ms"); + ServiceMain.main(args); + } + } catch (Throwable e) { + logger.log(ERROR, "Unexpected exception in free-pid1 init, shutting down... ", e); + System.exit(1); + } finally { + stopInitDServices(); + } + } + + static void initSysctl() { + try { + Path sysctlD = Paths.get("/etc/sysctl.d/"); + for (Path conf : Files.newDirectoryStream(sysctlD, "*.conf")) { + try { + new ProcessBuilder("/usr/sbin/sysctl", "-p", conf.toString()).start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + static void startInitDService(String serviceName, boolean stopOnShutdown) { + Path serviceInit = Paths.get("/etc/init.d/", serviceName); + if (Files.exists(serviceInit)) + try { + int exitCode = new ProcessBuilder(serviceInit.toString(), "start").start().waitFor(); + if (exitCode != 0) + logger.log(ERROR, "Service " + serviceName + " dit not stop properly"); + else + logger.log(DEBUG, "Service " + serviceName + " started"); + if (stopOnShutdown) + initDServices.add(serviceName); +// Runtime.getRuntime().addShutdownHook(new Thread(() -> { +// try { +// new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor(); +// } catch (IOException | InterruptedException e) { +// e.printStackTrace(); +// } +// }, "FREEd stop service " + serviceName)); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + else + logger.log(WARNING, "Service " + serviceName + " not found and therefore not started"); + } + + static boolean waitForNetwork(long timeout) { + long begin = System.currentTimeMillis(); + long duration = 0; + boolean networkAvailable = false; + try { + networkAvailable: while (!networkAvailable) { + duration = System.currentTimeMillis() - begin; + if (duration > timeout) + break networkAvailable; + Enumeration netInterfaces = null; + try { + netInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + throw new IllegalStateException("Cannot list network interfaces", e); + } + if (netInterfaces != null) { + while (netInterfaces.hasMoreElements()) { + NetworkInterface netInterface = netInterfaces.nextElement(); + logger.log(DEBUG, "Interface:" + netInterface); + for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) { + InetAddress inetAddr = addr.getAddress(); + logger.log(DEBUG, " addr: " + inetAddr); + if (!inetAddr.isLoopbackAddress() && !inetAddr.isLinkLocalAddress()) { + try { + if (inetAddr.isReachable((int) timeout)) { + networkAvailable = true; + duration = System.currentTimeMillis() - begin; + logger.log(DEBUG, + "Network available after " + duration + " ms. IP: " + inetAddr); + break networkAvailable; + } + } catch (IOException e) { + logger.log(ERROR, "Cannot check whether " + inetAddr + " is reachable", e); + } + } + } + } + } else { + throw new IllegalStateException("No network interface has been found"); + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // silent + } + } + } catch (Exception e) { + logger.log(ERROR, "Cannot check whether network is available", e); + } + return networkAvailable; + } + + static void shutdown(boolean reboot) { + try { + stopInitDServices(); + Path sysrqP = Paths.get("/proc/sys/kernel/sysrq"); + Files.writeString(sysrqP, "1"); + Path sysrqTriggerP = Paths.get("/proc/sysrq-trigger"); + Files.writeString(sysrqTriggerP, "e");// send SIGTERM to all processes + // Files.writeString(sysrqTriggerP, "i");// send SIGKILL to all processes + Files.writeString(sysrqTriggerP, "e");// flush data to disk + Files.writeString(sysrqTriggerP, "u");// unmount + if (reboot) + Files.writeString(sysrqTriggerP, "b"); + else + Files.writeString(sysrqTriggerP, "o"); + } catch (IOException e) { + logger.log(ERROR, "Cannot shut down system", e); + } + } + + static void stopInitDServices() { + for (int i = initDServices.size() - 1; i >= 0; i--) { + String serviceName = initDServices.get(i); + Path serviceInit = Paths.get("/etc/init.d/", serviceName); + try { + int exitCode = new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor(); + if (exitCode != 0) + logger.log(ERROR, "Service " + serviceName + " dit not stop properly"); + } catch (InterruptedException | IOException e) { + logger.log(ERROR, "Cannot stop service " + serviceName, e); + } + } + } + + /** A thread watching the login prompt. */ + static class LoginThread extends Thread { + private boolean systemShuttingDown = false; + private Process process = null; + + public LoginThread() { + super("FREEd login prompt"); + setDaemon(true); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + systemShuttingDown = true; + if (process != null) + process.destroy(); + })); + } + + @Override + public void run() { + boolean getty = true; + prompt: while (!systemShuttingDown) { + try { + if (getty) { + ProcessBuilder pb = new ProcessBuilder("/usr/sbin/getty", "38400", "tty2"); + process = pb.start(); + } else { + Console console = System.console(); + console.readLine(); // type return once to activate login prompt + console.printf("login: "); + String username = console.readLine(); + username = username.trim(); + if ("".equals(username)) + continue prompt; + ProcessBuilder pb = new ProcessBuilder("su", "--login", username); + pb.redirectError(ProcessBuilder.Redirect.INHERIT); + pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); + pb.redirectInput(ProcessBuilder.Redirect.INHERIT); + process = pb.start(); + } + Runtime.getRuntime().addShutdownHook(new Thread(() -> process.destroy())); + try { + process.waitFor(); + } catch (InterruptedException e) { + process.destroy(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + process = null; + } + } + } + + } +} diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java index ff602ad51..30a149bc9 100644 --- a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java +++ b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java @@ -29,8 +29,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; -import org.argeo.init.RuntimeContext; -import org.argeo.init.Service; +import org.argeo.api.init.RuntimeContext; +import org.argeo.internal.init.InternalState; /** * A thin logging system based on the {@link Logger} framework. It is a @@ -150,7 +150,7 @@ class ThinLogging implements Consumer> { } private void close() { - RuntimeContext runtimeContext = Service.getRuntimeContext(); + RuntimeContext runtimeContext = InternalState.getMainRuntimeContext(); if (runtimeContext != null) { try { runtimeContext.waitForStop(0); @@ -609,24 +609,24 @@ class ThinLogging implements Consumer> { } } - public static void main(String args[]) { - Logger logger = System.getLogger(ThinLogging.class.getName()); - logger.log(Logger.Level.ALL, "Log all"); - logger.log(Logger.Level.TRACE, "Multi\nline\ntrace"); - logger.log(Logger.Level.DEBUG, "Log debug"); - logger.log(Logger.Level.INFO, "Log info"); - logger.log(Logger.Level.WARNING, "Log warning"); - logger.log(Logger.Level.ERROR, "Log exception", new Throwable()); - - try { - // we ait a bit in order to make sure all messages are flushed - // TODO synchronize more efficiently - // executor.awaitTermination(300, TimeUnit.MILLISECONDS); - ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // silent - } - - } +// public static void main(String args[]) { +// Logger logger = System.getLogger(ThinLogging.class.getName()); +// logger.log(Logger.Level.ALL, "Log all"); +// logger.log(Logger.Level.TRACE, "Multi\nline\ntrace"); +// logger.log(Logger.Level.DEBUG, "Log debug"); +// logger.log(Logger.Level.INFO, "Log info"); +// logger.log(Logger.Level.WARNING, "Log warning"); +// logger.log(Logger.Level.ERROR, "Log exception", new Throwable()); +// +// try { +// // we wait a bit in order to make sure all messages are flushed +// // TODO synchronize more efficiently +// // executor.awaitTermination(300, TimeUnit.MILLISECONDS); +// ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS); +// } catch (InterruptedException e) { +// // silent +// } +// +// } } diff --git a/org.argeo.init/src/org/argeo/init/osgi/Activator.java b/org.argeo.init/src/org/argeo/init/osgi/Activator.java index b85b248b9..057c21786 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/Activator.java +++ b/org.argeo.init/src/org/argeo/init/osgi/Activator.java @@ -4,7 +4,7 @@ import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.util.Objects; -import org.argeo.init.Service; +import org.argeo.init.ServiceMain; import org.argeo.init.logging.ThinLoggerFinder; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; @@ -29,7 +29,7 @@ public class Activator implements BundleActivator { public void start(final BundleContext bundleContext) throws Exception { // The OSGi runtime was created by us, and therefore already initialized - argeoInit = Boolean.parseBoolean(bundleContext.getProperty(Service.PROP_ARGEO_INIT_MAIN)); + argeoInit = Boolean.parseBoolean(bundleContext.getProperty(ServiceMain.PROP_ARGEO_INIT_MAIN)); if (!argeoInit) { if (runtimeContext == null) { runtimeContext = new OsgiRuntimeContext(bundleContext); 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 f5b260cab..67ee7b6df 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java @@ -20,8 +20,9 @@ import java.util.SortedMap; import java.util.StringTokenizer; import java.util.TreeMap; -import org.argeo.init.a2.A2Source; -import org.argeo.init.a2.ProvisioningManager; +import org.argeo.api.a2.A2Source; +import org.argeo.api.a2.ProvisioningManager; +import org.argeo.api.init.InitConstants; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; @@ -37,10 +38,6 @@ import org.osgi.framework.wiring.FrameworkWiring; * methods, configured via properties. */ public class OsgiBoot implements OsgiBootConstants { - public final static String PROP_ARGEO_OSGI_START = "argeo.osgi.start"; - public final static String PROP_ARGEO_OSGI_MAX_START_LEVEL = "argeo.osgi.maxStartLevel"; - public final static String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources"; - @Deprecated final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles"; final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl"; @@ -59,18 +56,6 @@ public class OsgiBoot implements OsgiBootConstants { public final static String DEFAULT_BASE_URL = "reference:file:"; final static String DEFAULT_MAX_START_LEVEL = "32"; - // OSGi standard properties - final static String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel"; - final static String PROP_OSGI_STARTLEVEL = "osgi.startLevel"; - public final static String PROP_OSGI_INSTANCE_AREA = "osgi.instance.area"; - public final static String PROP_OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; - public final static String PROP_OSGI_SHARED_CONFIGURATION_AREA = "osgi.sharedConfiguration.area"; - public final static String PROP_OSGI_USE_SYSTEM_PROPERTIES = "osgi.framework.useSystemProperties"; - - // Symbolic names - final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.init"; - final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi"; - private final BundleContext bundleContext; private final String localCache; private final ProvisioningManager provisioningManager; @@ -86,7 +71,7 @@ public class OsgiBoot implements OsgiBootConstants { localCache = getProperty(PROP_ARGEO_OSGI_LOCAL_CACHE, homeUri + ".m2/repository/"); provisioningManager = new ProvisioningManager(bundleContext); - String sources = getProperty(PROP_ARGEO_OSGI_SOURCES); + String sources = getProperty(InitConstants.PROP_ARGEO_OSGI_SOURCES); if (sources == null) { provisioningManager.registerDefaultSource(); } else { @@ -141,9 +126,9 @@ public class OsgiBoot implements OsgiBootConstants { long begin = System.currentTimeMillis(); // notify start - String osgiInstancePath = getProperty(PROP_OSGI_INSTANCE_AREA); - String osgiConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA); - String osgiSharedConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA); + String osgiInstancePath = getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA); + String osgiConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA); + String osgiSharedConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA); OsgiBootUtils.info("OSGi bootstrap starting" // + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") // + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") // @@ -238,8 +223,8 @@ public class OsgiBoot implements OsgiBootConstants { Bundle bundle = (Bundle) installedBundles.get(url); if (OsgiBootUtils.isDebug()) debug("Bundle " + bundle.getSymbolicName() + " already installed from " + url); - } else if (url.contains("/" + SYMBOLIC_NAME_EQUINOX + "/") - || url.contains("/" + SYMBOLIC_NAME_OSGI_BOOT + "/")) { + } else if (url.contains("/" + InitConstants.SYMBOLIC_NAME_EQUINOX + "/") + || url.contains("/" + InitConstants.SYMBOLIC_NAME_INIT + "/")) { if (OsgiBootUtils.isDebug()) warn("Skip " + url); return; @@ -285,8 +270,8 @@ public class OsgiBoot implements OsgiBootConstants { } catch (BundleException e) { final String ALREADY_INSTALLED = "is already installed"; String message = e.getMessage(); - if ((message.contains("Bundle \"" + SYMBOLIC_NAME_OSGI_BOOT + "\"") - || message.contains("Bundle \"" + SYMBOLIC_NAME_EQUINOX + "\"")) + if ((message.contains("Bundle \"" + InitConstants.SYMBOLIC_NAME_INIT + "\"") + || message.contains("Bundle \"" + InitConstants.SYMBOLIC_NAME_EQUINOX + "\"")) && message.contains(ALREADY_INSTALLED)) { // silent, in order to avoid warnings: we know that both // have already been installed... @@ -317,15 +302,15 @@ public class OsgiBoot implements OsgiBootConstants { if (properties != null) { for (String key : properties.keySet()) { String property = key; - if (property.startsWith(PROP_ARGEO_OSGI_START)) { + 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(PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL)); + int maxStartLevel = Integer.parseInt(getProperty(InitConstants.PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL)); for (int i = 1; i <= maxStartLevel; i++) { - String key = PROP_ARGEO_OSGI_START + "." + i; + String key = InitConstants.PROP_ARGEO_OSGI_START + "." + i; String value = getProperty(key); if (value != null) map.put(key, value); @@ -333,7 +318,7 @@ public class OsgiBoot implements OsgiBootConstants { } // finally, override with system properties for (Object key : System.getProperties().keySet()) { - if (key.toString().startsWith(PROP_ARGEO_OSGI_START)) { + if (key.toString().startsWith(InitConstants.PROP_ARGEO_OSGI_START)) { map.put(key.toString(), System.getProperty(key.toString())); } } @@ -348,7 +333,7 @@ public class OsgiBoot implements OsgiBootConstants { if (properties != null) { for (Object key : properties.keySet()) { String property = key.toString(); - if (property.startsWith(PROP_ARGEO_OSGI_START)) { + if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) { map.put(property, properties.get(property).toString()); } } @@ -356,18 +341,18 @@ public class OsgiBoot implements OsgiBootConstants { startBundles(map); } - /** Start bundle based on keys starting with {@link #PROP_ARGEO_OSGI_START}. */ + /** Start bundle based on keys starting with {@link InitConstants#PROP_ARGEO_OSGI_START}. */ protected void doStartBundles(Map properties) { FrameworkStartLevel frameworkStartLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class); // default and active start levels from System properties int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel(); - int defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4")); - int activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6")); + int defaultStartLevel = Integer.parseInt(getProperty(InitConstants.PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4")); + int activeStartLevel = Integer.parseInt(getProperty(InitConstants.PROP_OSGI_STARTLEVEL, "6")); if (OsgiBootUtils.isDebug()) { OsgiBootUtils.debug("OSGi default start level: " - + getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "") + ", using " + defaultStartLevel); - OsgiBootUtils.debug("OSGi active start level: " + getProperty(PROP_OSGI_STARTLEVEL, "") + + getProperty(InitConstants.PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "") + ", using " + defaultStartLevel); + OsgiBootUtils.debug("OSGi active start level: " + getProperty(InitConstants.PROP_OSGI_STARTLEVEL, "") + ", using " + activeStartLevel); OsgiBootUtils.debug("Framework start level: " + frameworkStartLevel.getStartLevel() + " (initial: " + initialStartLevel + ")"); @@ -454,11 +439,11 @@ public class OsgiBoot implements OsgiBootConstants { Integer defaultStartLevel) { // default (and previously, only behaviour) - appendToStartLevels(startLevels, defaultStartLevel, properties.getOrDefault(PROP_ARGEO_OSGI_START, "")); + appendToStartLevels(startLevels, defaultStartLevel, properties.getOrDefault(InitConstants.PROP_ARGEO_OSGI_START, "")); // list argeo.osgi.start.* system properties Iterator keys = properties.keySet().iterator(); - final String prefix = PROP_ARGEO_OSGI_START + "."; + final String prefix = InitConstants.PROP_ARGEO_OSGI_START + "."; while (keys.hasNext()) { String key = keys.next(); if (key.startsWith(prefix)) { diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java index cd2c80acb..6eed6db53 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java @@ -12,6 +12,7 @@ import java.util.Properties; import java.util.Set; import java.util.TreeMap; +import org.argeo.api.init.InitConstants; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; @@ -38,8 +39,8 @@ public class OsgiBuilder { public OsgiBuilder() { // configuration.put("osgi.clean", "true"); - configuration.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA)); - configuration.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA)); + configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, System.getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA)); + configuration.put(InitConstants.PROP_OSGI_INSTANCE_AREA, System.getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA)); configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN)); } @@ -48,7 +49,7 @@ public class OsgiBuilder { framework = OsgiBootUtils.launch(configuration); BundleContext bc = framework.getBundleContext(); - String osgiData = bc.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA); + String osgiData = bc.getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA); // String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP); String osgiConf = framework.getDataFile("").getAbsolutePath(); if (OsgiBootUtils.isDebug()) @@ -278,7 +279,7 @@ public class OsgiBuilder { private Properties startLevelsToProperties() { Properties properties = new Properties(); for (Integer startLevel : startLevels.keySet()) { - String property = OsgiBoot.PROP_ARGEO_OSGI_START + "." + startLevel; + String property = InitConstants.PROP_ARGEO_OSGI_START + "." + startLevel; StringBuilder value = new StringBuilder(); for (String bundle : startLevels.get(startLevel).getBundles()) { value.append(bundle); 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 8afe05922..c35046b20 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java @@ -12,7 +12,7 @@ import java.util.concurrent.Flow; import java.util.function.Consumer; import java.util.function.Supplier; -import org.argeo.init.RuntimeContext; +import org.argeo.api.init.RuntimeContext; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; diff --git a/org.argeo.init/src/org/argeo/internal/init/InternalState.java b/org.argeo.init/src/org/argeo/internal/init/InternalState.java new file mode 100644 index 000000000..03ab75005 --- /dev/null +++ b/org.argeo.init/src/org/argeo/internal/init/InternalState.java @@ -0,0 +1,21 @@ +package org.argeo.internal.init; + +import org.argeo.api.init.RuntimeContext; + +/** + * Keep track of the internal state mostly with static variables, typically in + * order to synchronise shutdown. + */ +public class InternalState { + private static RuntimeContext mainRuntimeContext; + + /** The root runtime context in this JVM. */ + public static RuntimeContext getMainRuntimeContext() { + return mainRuntimeContext; + } + + public static void setMainRuntimeContext(RuntimeContext mainRuntimeContext) { + InternalState.mainRuntimeContext = mainRuntimeContext; + } + +} -- 2.30.2