From: Mathieu Baudier Date: Tue, 5 Mar 2024 09:14:20 +0000 (+0100) Subject: Refactor Argeo init X-Git-Tag: v2.3.28~34 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=722dc6b20b15014e3d963bd09617ddfd9b63a89f Refactor Argeo init --- 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/api/a2/A2Branch.java b/org.argeo.init/src/org/argeo/api/a2/A2Branch.java new file mode 100644 index 000000000..1db82a74a --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/A2Branch.java @@ -0,0 +1,89 @@ +package org.argeo.api.a2; + +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.argeo.init.osgi.OsgiBootUtils; +import org.osgi.framework.Version; + +/** + * A logical linear sequence of versions of a given {@link A2Component}. This is + * typically a combination of major and minor version, indicating backward + * compatibility. + */ +public class A2Branch implements Comparable { + private final A2Component component; + private final String id; + + final SortedMap modules = Collections.synchronizedSortedMap(new TreeMap<>()); + + public A2Branch(A2Component component, String id) { + this.component = component; + this.id = id; + component.branches.put(id, this); + } + + public Iterable listModules(Object filter) { + return modules.values(); + } + + A2Module getOrAddModule(Version version, Object locator) { + if (modules.containsKey(version)) { + A2Module res = modules.get(version); + if (OsgiBootUtils.isDebug() && !res.getLocator().equals(locator)) { + OsgiBootUtils.debug("Inconsistent locator " + locator + " (registered: " + res.getLocator() + ")"); + } + return res; + } else + return new A2Module(this, version, locator); + } + + public A2Module last() { + return modules.get(modules.lastKey()); + } + + public A2Module first() { + return modules.get(modules.firstKey()); + } + + public A2Component getComponent() { + return component; + } + + public String getId() { + return id; + } + + @Override + public int compareTo(A2Branch o) { + return id.compareTo(id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof A2Branch) { + A2Branch o = (A2Branch) obj; + return component.equals(o.component) && id.equals(o.id); + } else + return false; + } + + @Override + public String toString() { + return getCoordinates(); + } + + public String getCoordinates() { + return component + ":" + id; + } + + static String versionToBranchId(Version version) { + return version.getMajor() + "." + version.getMinor(); + } +} diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Component.java b/org.argeo.init/src/org/argeo/api/a2/A2Component.java new file mode 100644 index 000000000..d8b7512bd --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/A2Component.java @@ -0,0 +1,105 @@ +package org.argeo.api.a2; + +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.osgi.framework.Version; + +/** + * The logical name of a software package. In OSGi's case this is + * Bundle-SymbolicName. This is the equivalent of Maven's artifact + * id. + */ +public class A2Component implements Comparable { + private final A2Contribution contribution; + private final String id; + + final SortedMap branches = Collections.synchronizedSortedMap(new TreeMap<>()); + + public A2Component(A2Contribution contribution, String id) { + this.contribution = contribution; + this.id = id; + contribution.components.put(id, this); + } + + public Iterable listBranches(Object filter) { + return branches.values(); + } + + A2Branch getOrAddBranch(String branchId) { + if (!branches.containsKey(branchId)) { + A2Branch a2Branch = new A2Branch(this, branchId); + branches.put(branchId, a2Branch); + } + return branches.get(branchId); + } + + A2Module getOrAddModule(Version version, Object locator) { + A2Branch branch = getOrAddBranch(A2Branch.versionToBranchId(version)); + A2Module module = branch.getOrAddModule(version, locator); + return module; + } + + public A2Branch last() { + return branches.get(branches.lastKey()); + } + + public A2Contribution getContribution() { + return contribution; + } + + public String getId() { + return id; + } + + @Override + public int compareTo(A2Component o) { + return id.compareTo(o.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof A2Component) { + A2Component o = (A2Component) obj; + return contribution.equals(o.contribution) && id.equals(o.id); + } else + return false; + } + + @Override + public String toString() { + return contribution.getId() + ":" + id; + } + + void asTree(String prefix, StringBuffer buf) { + if (prefix == null) + prefix = ""; + A2Branch lastBranch = last(); + SortedMap displayMap = new TreeMap<>(Collections.reverseOrder()); + displayMap.putAll(branches); + for (String branchId : displayMap.keySet()) { + A2Branch branch = displayMap.get(branchId); + if (!lastBranch.equals(branch)) { + buf.append('\n'); + buf.append(prefix); + } else { + buf.append(" -"); + } + buf.append(prefix); + buf.append(branchId); + A2Module first = branch.first(); + A2Module last = branch.last(); + buf.append(" (").append(last.getVersion()); + if (!first.equals(last)) + buf.append(" ... ").append(first.getVersion()); + buf.append(')'); + } + } + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Contribution.java b/org.argeo.init/src/org/argeo/api/a2/A2Contribution.java new file mode 100644 index 000000000..f099ceb2b --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/A2Contribution.java @@ -0,0 +1,151 @@ +package org.argeo.api.a2; + +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +/** + * A category grouping a set of {@link A2Component}, typically based on the + * provider of these components. This is the equivalent of Maven's group Id. + */ +public class A2Contribution implements Comparable { + final static String BOOT = "boot"; + final static String RUNTIME = "runtime"; + final static String CLASSPATH = "classpath"; + + final static String DEFAULT = "default"; + final static String LIB = "lib"; + + private final ProvisioningSource source; + private final String id; + + final Map components = Collections.synchronizedSortedMap(new TreeMap<>()); + + /** + * The contribution must be added to the source. Rather use + * {@link AbstractProvisioningSource#getOrAddContribution(String)} than this + * contructor directly. + */ + public A2Contribution(ProvisioningSource context, String id) { + this.source = context; + this.id = id; +// if (context != null) +// context.contributions.put(id, this); + } + + public Iterable listComponents(Object filter) { + return components.values(); + } + + A2Component getOrAddComponent(String componentId) { + if (components.containsKey(componentId)) + return components.get(componentId); + else + return new A2Component(this, componentId); + } + + public ProvisioningSource getSource() { + return source; + } + + public String getId() { + return id; + } + + @Override + public int compareTo(A2Contribution o) { + return id.compareTo(o.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof A2Contribution) { + A2Contribution o = (A2Contribution) obj; + return id.equals(o.id); + } else + return false; + } + + @Override + public String toString() { + return id; + } + + void asTree(String prefix, StringBuffer buf) { + if (prefix == null) + prefix = ""; + for (String componentId : components.keySet()) { + buf.append(prefix); + buf.append(componentId); + A2Component component = components.get(componentId); + component.asTree(prefix, buf); + buf.append('\n'); + } + } + + static String localOsArchRelativePath() { + return Os.local().toString() + "/" + Arch.local().toString(); + } + + static enum Os { + LINUX, WIN32, MACOSX, UNKOWN; + + @Override + public String toString() { + return name().toLowerCase(); + } + + public static Os local() { + String osStr = System.getProperty("os.name").toLowerCase(); + if (osStr.startsWith("linux")) + return LINUX; + if (osStr.startsWith("win")) + return WIN32; + if (osStr.startsWith("mac")) + return MACOSX; + return UNKOWN; + } + + } + + static enum Arch { + X86_64, AARCH64, X86, POWERPC, UNKOWN; + + @Override + public String toString() { + return name().toLowerCase(); + } + + public static Arch local() { + String archStr = System.getProperty("os.arch").toLowerCase(); + return switch (archStr) { + case "x86_64": + case "amd64": + case "x86-64": { + yield X86_64; + } + case "aarch64": + case "arm64": { + yield AARCH64; + } + case "x86": + case "i386": + case "i686": { + yield X86; + } + case "powerpc": + case "ppc": { + yield POWERPC; + } + default: + yield UNKOWN; + }; + } + } + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Exception.java b/org.argeo.init/src/org/argeo/api/a2/A2Exception.java new file mode 100644 index 000000000..2e609f0f4 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/A2Exception.java @@ -0,0 +1,15 @@ +package org.argeo.api.a2; + +/** Unchecked A2 provisioning exception. */ +public class A2Exception extends RuntimeException { + private static final long serialVersionUID = 1927603558545397360L; + + public A2Exception(String message, Throwable e) { + super(message, e); + } + + public A2Exception(String message) { + super(message); + } + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Module.java b/org.argeo.init/src/org/argeo/api/a2/A2Module.java new file mode 100644 index 000000000..4ad43625f --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/A2Module.java @@ -0,0 +1,62 @@ +package org.argeo.api.a2; + +import org.osgi.framework.Version; + +/** + * An identified software package. In OSGi's case this is the combination of + * Bundle-SymbolicName and Bundle-version. This is the + * equivalent of the full coordinates of a Maven artifact version. + */ +public class A2Module implements Comparable { + private final A2Branch branch; + private final Version version; + private final Object locator; + + public A2Module(A2Branch branch, Version version, Object locator) { + this.branch = branch; + this.version = version; + this.locator = locator; + branch.modules.put(version, this); + } + + public A2Branch getBranch() { + return branch; + } + + public Version getVersion() { + return version; + } + + Object getLocator() { + return locator; + } + + @Override + public int compareTo(A2Module o) { + return version.compareTo(o.version); + } + + @Override + public int hashCode() { + return version.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof A2Module) { + A2Module o = (A2Module) obj; + return branch.equals(o.branch) && version.equals(o.version); + } else + return false; + } + + @Override + public String toString() { + return getCoordinates(); + } + + public String getCoordinates() { + return branch.getComponent() + ":" + version; + } + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Source.java b/org.argeo.init/src/org/argeo/api/a2/A2Source.java new file mode 100644 index 000000000..5883e9d99 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/A2Source.java @@ -0,0 +1,21 @@ +package org.argeo.api.a2; + +import java.net.URI; + +/** A provisioning source in A2 format. */ +public interface A2Source extends ProvisioningSource { + /** Use standard a2 protocol, installing from source URL. */ + final static String SCHEME_A2 = "a2"; + /** + * Use equinox-specific reference: installation, which does not copy the bundle + * content. + */ + final static String SCHEME_A2_REFERENCE = "a2+reference"; + final static String DEFAULT_A2_URI = SCHEME_A2 + ":///"; + final static String DEFAULT_A2_REFERENCE_URI = SCHEME_A2_REFERENCE + ":///"; + + final static String INCLUDE = "include"; + final static String EXCLUDE = "exclude"; + + URI getUri(); +} diff --git a/org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java b/org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java new file mode 100644 index 000000000..62416fe48 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java @@ -0,0 +1,269 @@ +package org.argeo.api.a2; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; + +/** Where components are retrieved from. */ +public abstract class AbstractProvisioningSource implements ProvisioningSource { + protected final Map contributions = Collections.synchronizedSortedMap(new TreeMap<>()); + + private final boolean usingReference; + + public AbstractProvisioningSource(boolean usingReference) { + this.usingReference = usingReference; + } + + public Iterable listContributions(Object filter) { + return contributions.values(); + } + + @Override + public Bundle install(BundleContext bc, A2Module module) { + try { + Object locator = module.getLocator(); + if (usingReference && locator instanceof Path locatorPath) { + String referenceUrl = "reference:file:" + locatorPath.toString(); + Bundle bundle = bc.installBundle(referenceUrl); + return bundle; + } else { + Path locatorPath = (Path) locator; + Path pathToUse; + boolean isTemp = false; + if (locator instanceof Path && Files.isDirectory(locatorPath)) { + pathToUse = toTempJar(locatorPath); + isTemp = true; + } else { + pathToUse = locatorPath; + } + Bundle bundle; + try (InputStream in = newInputStream(pathToUse)) { + bundle = bc.installBundle(locatorPath.toAbsolutePath().toString(), in); + } + + if (isTemp && pathToUse != null) + Files.deleteIfExists(pathToUse); + return bundle; + } + } catch (BundleException | IOException e) { + throw new A2Exception("Cannot install module " + module, e); + } + } + + @Override + public void update(Bundle bundle, A2Module module) { + try { + Object locator = module.getLocator(); + if (usingReference && locator instanceof Path) { + try (InputStream in = newInputStream(locator)) { + bundle.update(in); + } + } else { + Path locatorPath = (Path) locator; + Path pathToUse; + boolean isTemp = false; + if (locator instanceof Path && Files.isDirectory(locatorPath)) { + pathToUse = toTempJar(locatorPath); + isTemp = true; + } else { + pathToUse = locatorPath; + } + try (InputStream in = newInputStream(pathToUse)) { + bundle.update(in); + } + if (isTemp && pathToUse != null) + Files.deleteIfExists(pathToUse); + } + } catch (BundleException | IOException e) { + throw new A2Exception("Cannot update module " + module, e); + } + } + + @Override + public A2Branch findBranch(String componentId, Version version) { + A2Component component = findComponent(componentId); + if (component == null) + return null; + String branchId = version.getMajor() + "." + version.getMinor(); + if (!component.branches.containsKey(branchId)) + return null; + return component.branches.get(branchId); + } + + protected A2Contribution getOrAddContribution(String contributionId) { + if (contributions.containsKey(contributionId)) + return contributions.get(contributionId); + else { + A2Contribution contribution = new A2Contribution(this, contributionId); + contributions.put(contributionId, contribution); + return contribution; + } + } + + protected void asTree(String prefix, StringBuffer buf) { + if (prefix == null) + prefix = ""; + for (String contributionId : contributions.keySet()) { + buf.append(prefix); + buf.append(contributionId); + buf.append('\n'); + A2Contribution contribution = contributions.get(contributionId); + contribution.asTree(prefix + " ", buf); + } + } + + protected void asTree() { + StringBuffer buf = new StringBuffer(); + asTree("", buf); + System.out.println(buf); + } + + protected A2Component findComponent(String componentId) { + SortedMap res = new TreeMap<>(); + for (A2Contribution contribution : contributions.values()) { + components: for (String componentIdKey : contribution.components.keySet()) { + if (componentId.equals(componentIdKey)) { + res.put(contribution, contribution.components.get(componentIdKey)); + break components; + } + } + } + if (res.size() == 0) + return null; + // TODO explicit contribution priorities + return res.get(res.lastKey()); + + } + + protected String[] readNameVersionFromModule(Path modulePath) { + Manifest manifest; + if (Files.isDirectory(modulePath)) { + manifest = findManifest(modulePath); + } else { + try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) { + manifest = in.getManifest(); + } catch (IOException e) { + throw new A2Exception("Cannot read manifest from " + modulePath, e); + } + } + String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION); + String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); + int semiColIndex = symbolicName.indexOf(';'); + if (semiColIndex >= 0) + symbolicName = symbolicName.substring(0, semiColIndex); + return new String[] { symbolicName, versionStr }; + } + + protected String readVersionFromModule(Path modulePath) { + Manifest manifest; + if (Files.isDirectory(modulePath)) { + manifest = findManifest(modulePath); + } else { + try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) { + manifest = in.getManifest(); + } catch (IOException e) { + throw new A2Exception("Cannot read manifest from " + modulePath, e); + } + } + String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION); + return versionStr; + } + + protected String readSymbolicNameFromModule(Path modulePath) { + Manifest manifest; + if (Files.isDirectory(modulePath)) { + manifest = findManifest(modulePath); + } else { + try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) { + manifest = in.getManifest(); + } catch (IOException e) { + throw new A2Exception("Cannot read manifest from " + modulePath, e); + } + } + String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); + int semiColIndex = symbolicName.indexOf(';'); + if (semiColIndex >= 0) + symbolicName = symbolicName.substring(0, semiColIndex); + return symbolicName; + } + + protected boolean isUsingReference() { + return usingReference; + } + + private InputStream newInputStream(Object locator) throws IOException { + if (locator instanceof Path) { + return Files.newInputStream((Path) locator); + } else if (locator instanceof URL) { + return ((URL) locator).openStream(); + } else { + throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass()); + } + } + + private static Manifest findManifest(Path currentPath) { + Path metaInfPath = currentPath.resolve("META-INF"); + if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) { + Path manifestPath = metaInfPath.resolve("MANIFEST.MF"); + try { + try (InputStream in = Files.newInputStream(manifestPath)) { + Manifest manifest = new Manifest(in); + return manifest; + } + } catch (IOException e) { + throw new A2Exception("Cannot read manifest from " + manifestPath, e); + } + } else { + Path parentPath = currentPath.getParent(); + if (parentPath == null) + throw new A2Exception("MANIFEST.MF file not found."); + return findManifest(currentPath.getParent()); + } + } + + private static Path toTempJar(Path dir) { + try { + Manifest manifest = findManifest(dir); + Path jarPath = Files.createTempFile("a2Source", ".jar"); + try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) { + Files.walkFileTree(dir, new SimpleFileVisitor() { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path relPath = dir.relativize(file); + // skip MANIFEST from folder + if (relPath.toString().contentEquals("META-INF/MANIFEST.MF")) + return FileVisitResult.CONTINUE; + zos.putNextEntry(new ZipEntry(relPath.toString())); + Files.copy(file, zos); + zos.closeEntry(); + return FileVisitResult.CONTINUE; + } + }); + } + return jarPath; + } catch (IOException e) { + throw new A2Exception("Cannot install OSGi bundle from " + dir, e); + } + + } + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java b/org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java new file mode 100644 index 000000000..2f1cf95fa --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java @@ -0,0 +1,43 @@ +package org.argeo.api.a2; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +import org.argeo.init.osgi.OsgiBootUtils; +import org.osgi.framework.Version; + +/** + * A provisioning source based on the linear classpath with which the JVM has + * been started. + */ +public class ClasspathSource extends AbstractProvisioningSource { + + public ClasspathSource() { + super(true); + } + + void load() throws IOException { + A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH); + List classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator)); + parts: for (String part : classpath) { + Path file = Paths.get(part); + Version version; + try { + version = new Version(readVersionFromModule(file)); + } catch (Exception e) { + // ignore non OSGi + continue parts; + } + String moduleName = readSymbolicNameFromModule(file); + A2Component component = classpathContribution.getOrAddComponent(moduleName); + A2Module module = component.getOrAddModule(version, file); + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Registered " + module); + } + + } +} diff --git a/org.argeo.init/src/org/argeo/api/a2/FsA2Source.java b/org.argeo.init/src/org/argeo/api/a2/FsA2Source.java new file mode 100644 index 000000000..ebb602049 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/FsA2Source.java @@ -0,0 +1,171 @@ +package org.argeo.api.a2; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.StringJoiner; +import java.util.TreeMap; + +import org.argeo.init.osgi.OsgiBootUtils; +import org.osgi.framework.Version; + +/** A file system {@link AbstractProvisioningSource} in A2 format. */ +public class FsA2Source extends AbstractProvisioningSource implements A2Source { + private final Path base; + private final Map variantsXOr; + + private final List includes; + private final List excludes; + + public FsA2Source(Path base, Map variantsXOr, boolean usingReference, List includes, + List excludes) { + super(usingReference); + this.base = base; + this.variantsXOr = new HashMap<>(variantsXOr); + this.includes = includes; + this.excludes = excludes; + } + + void load() throws IOException { + SortedMap contributions = new TreeMap<>(); + + DirectoryStream contributionPaths = Files.newDirectoryStream(base); + contributions: for (Path contributionPath : contributionPaths) { + if (Files.isDirectory(contributionPath)) { + String contributionId = contributionPath.getFileName().toString(); + if (A2Contribution.BOOT.equals(contributionId))// skip boot + continue contributions; + if (contributionId.contains(".")) { + A2Contribution contribution = getOrAddContribution(contributionId); + contributions.put(contributionPath, contribution); + } else {// variants + Path variantPath = null; + // is it an explicit variant? + String variant = variantsXOr.get(contributionPath.getFileName().toString()); + if (variant != null) { + variantPath = contributionPath.resolve(variant); + } + + // is there a default variant? + if (variantPath == null) { + Path defaultPath = contributionPath.resolve(A2Contribution.DEFAULT); + if (Files.exists(defaultPath)) { + variantPath = defaultPath; + } + } + + if (variantPath == null) + continue contributions; + + // a variant was found, let's collect its contributions (also common ones in its + // parent) + if (Files.exists(variantPath.getParent())) { + for (Path variantContributionPath : Files.newDirectoryStream(variantPath.getParent())) { + String variantContributionId = variantContributionPath.getFileName().toString(); + if (variantContributionId.contains(".")) { + A2Contribution contribution = getOrAddContribution(variantContributionId); + contributions.put(variantContributionPath, contribution); + } + } + } + if (Files.exists(variantPath)) { + for (Path variantContributionPath : Files.newDirectoryStream(variantPath)) { + String variantContributionId = variantContributionPath.getFileName().toString(); + if (variantContributionId.contains(".")) { + A2Contribution contribution = getOrAddContribution(variantContributionId); + contributions.put(variantContributionPath, contribution); + } + } + } + } + } + } + + contributions: for (Path contributionPath : contributions.keySet()) { + String contributionId = contributionPath.getFileName().toString(); + if (includes != null && !includes.contains(contributionId)) + continue contributions; + if (excludes != null && excludes.contains(contributionId)) + continue contributions; + A2Contribution contribution = getOrAddContribution(contributionId); + DirectoryStream modulePaths = Files.newDirectoryStream(contributionPath); + modules: for (Path modulePath : modulePaths) { + if (!Files.isDirectory(modulePath)) { + // OsgiBootUtils.debug("Registering " + modulePath); + String moduleFileName = modulePath.getFileName().toString(); + int lastDot = moduleFileName.lastIndexOf('.'); + String ext = moduleFileName.substring(lastDot + 1); + if (!"jar".equals(ext)) + continue modules; + Version version; + // TODO optimise? check attributes? + String[] nameVersion = readNameVersionFromModule(modulePath); + String componentName = nameVersion[0]; + String versionStr = nameVersion[1]; + if (versionStr != null) { + version = new Version(versionStr); + } else { + OsgiBootUtils.debug("Ignore " + modulePath + " since version cannot be found"); + continue modules; + } +// } + A2Component component = contribution.getOrAddComponent(componentName); + A2Module module = component.getOrAddModule(version, modulePath); + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Registered " + module); + } + } + } + + } + + @Override + public URI getUri() { + URI baseUri = base.toUri(); + try { + if (baseUri.getScheme().equals("file")) { + String queryPart = ""; + if (!getVariantsXOr().isEmpty()) { + StringJoiner sj = new StringJoiner("&"); + for (String key : getVariantsXOr().keySet()) { + sj.add(key + "=" + getVariantsXOr().get(key)); + } + queryPart = sj.toString(); + } + return new URI(isUsingReference() ? SCHEME_A2_REFERENCE : SCHEME_A2, null, base.toString(), queryPart, + null); + } else { + throw new UnsupportedOperationException("Unsupported scheme " + baseUri.getScheme()); + } + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot build URI from " + baseUri, e); + } + } + + protected Map getVariantsXOr() { + return variantsXOr; + } + +// public static void main(String[] args) { +// if (args.length == 0) +// throw new IllegalArgumentException("Usage: "); +// try { +// Map xOr = new HashMap<>(); +// xOr.put("osgi", "equinox"); +// xOr.put("swt", "rap"); +// FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr); +// context.load(); +// context.asTree(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/FsM2Source.java b/org.argeo.init/src/org/argeo/api/a2/FsM2Source.java new file mode 100644 index 000000000..f63a90468 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/FsM2Source.java @@ -0,0 +1,66 @@ +package org.argeo.api.a2; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import org.argeo.init.osgi.OsgiBootUtils; +import org.osgi.framework.Version; + +/** A file system {@link AbstractProvisioningSource} in Maven 2 format. */ +public class FsM2Source extends AbstractProvisioningSource { + private final Path base; + + public FsM2Source(Path base) { + super(false); + this.base = base; + } + + void load() throws IOException { + Files.walkFileTree(base, new ArtifactFileVisitor()); + } + + class ArtifactFileVisitor extends SimpleFileVisitor { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + // OsgiBootUtils.debug("Processing " + file); + if (file.toString().endsWith(".jar")) { + Version version; + try { + version = new Version(readVersionFromModule(file)); + } catch (Exception e) { + // ignore non OSGi + return FileVisitResult.CONTINUE; + } + String moduleName = readSymbolicNameFromModule(file); + Path groupPath = file.getParent().getParent().getParent(); + Path relGroupPath = base.relativize(groupPath); + String contributionName = relGroupPath.toString().replace(File.separatorChar, '.'); + A2Contribution contribution = getOrAddContribution(contributionName); + A2Component component = contribution.getOrAddComponent(moduleName); + A2Module module = component.getOrAddModule(version, file); + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Registered " + module); + } + return super.visitFile(file, attrs); + } + + } + + public static void main(String[] args) { + try { + FsM2Source context = new FsM2Source(Paths.get("/home/mbaudier/.m2/repository")); + context.load(); + context.asTree(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/OsgiContext.java b/org.argeo.init/src/org/argeo/api/a2/OsgiContext.java new file mode 100644 index 000000000..dfed170d1 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/OsgiContext.java @@ -0,0 +1,48 @@ +package org.argeo.api.a2; + +import org.argeo.init.osgi.OsgiBootUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.Version; + +/** + * A running OSGi bundle context seen as a {@link AbstractProvisioningSource}. + */ +class OsgiContext extends AbstractProvisioningSource { + private final BundleContext bc; + + private A2Contribution runtimeContribution; + + public OsgiContext(BundleContext bc) { + super(false); + this.bc = bc; + runtimeContribution = getOrAddContribution(A2Contribution.RUNTIME); + } + + public OsgiContext() { + super(false); + Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class); + if (bundle == null) + throw new IllegalArgumentException( + "OSGi Boot bundle must be started or a bundle context must be specified"); + this.bc = bundle.getBundleContext(); + } + + void load() { + for (Bundle bundle : bc.getBundles()) { + registerBundle(bundle); + } + + } + + void registerBundle(Bundle bundle) { + String componentId = bundle.getSymbolicName(); + Version version = bundle.getVersion(); + A2Component component = runtimeContribution.getOrAddComponent(componentId); + A2Module module = component.getOrAddModule(version, bundle); + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Registered bundle module " + module + " (location id: " + bundle.getLocation() + ")"); + + } +} diff --git a/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java new file mode 100644 index 000000000..104f08a5d --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java @@ -0,0 +1,303 @@ +package org.argeo.api.a2; + +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; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.argeo.init.osgi.OsgiBootUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; +import org.osgi.framework.wiring.FrameworkWiring; + +/** Loads provisioning sources into an OSGi context. */ +public class ProvisioningManager { + BundleContext bc; + OsgiContext osgiContext; + List sources = Collections.synchronizedList(new ArrayList<>()); + + public ProvisioningManager(BundleContext bc) { + this.bc = bc; + osgiContext = new OsgiContext(bc); + osgiContext.load(); + } + + protected void addSource(ProvisioningSource source) { + sources.add(source); + } + + void installWholeSource(ProvisioningSource source) { + Set updatedBundles = new HashSet<>(); + for (A2Contribution contribution : source.listContributions(null)) { + for (A2Component component : contribution.components.values()) { + A2Module module = component.last().last(); + Bundle bundle = installOrUpdate(module); + if (bundle != null) + updatedBundles.add(bundle); + } + } +// FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); +// frameworkWiring.refreshBundles(updatedBundles); + } + + public void registerSource(String uri) { + try { + URI u = new URI(uri); + + // XOR + Map> properties = queryToMap(u); + Map xOr = new HashMap<>(); + List includes = null; + List excludes = null; + for (String key : properties.keySet()) { + List lst = properties.get(key); + if (A2Source.INCLUDE.equals(key)) { + includes = new ArrayList<>(lst); + } else if (A2Source.EXCLUDE.equals(key)) { + excludes = new ArrayList<>(lst); + } else { + if (lst.size() != 1) + throw new IllegalArgumentException("Invalid XOR definitions in " + uri); + xOr.put(key, lst.get(0)); + } + } + + if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) { + if (u.getHost() == null || "".equals(u.getHost())) { + String baseStr = u.getPath(); + if (File.separatorChar == '\\') {// MS Windows + baseStr = baseStr.substring(1).replace('/', File.separatorChar); + } + Path base = Paths.get(baseStr); + if (Files.exists(base)) { + FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()), + includes, excludes); + source.load(); + addSource(source); + OsgiBootUtils.info("Registered " + uri + " as source"); + + // OS specific / native + String localRelPath = A2Contribution.localOsArchRelativePath(); + Path localLibBase = base.resolve(A2Contribution.LIB).resolve(localRelPath); + if (Files.exists(localLibBase)) { + FsA2Source libSource = new FsA2Source(localLibBase, xOr, + SCHEME_A2_REFERENCE.equals(u.getScheme()), includes, excludes); + libSource.load(); + addSource(libSource); + OsgiBootUtils.info("Registered OS-specific " + uri + " as source (" + localRelPath + ")"); + } + } else { + OsgiBootUtils.debug("Source " + base + " does not exist, ignoring."); + } + } else { + throw new UnsupportedOperationException( + "Remote installation is not yet supported, cannot add source " + u); + } + } else { + throw new IllegalArgumentException("Unkown scheme: for source " + u); + } + } catch (Exception e) { + throw new A2Exception("Cannot add source " + uri, e); + } + } + + public boolean registerDefaultSource() { + String frameworkLocation = bc.getProperty("osgi.framework"); + try { + URI frameworkLocationUri = new URI(frameworkLocation); + if ("file".equals(frameworkLocationUri.getScheme())) { + Path frameworkPath = Paths.get(frameworkLocationUri); + if (frameworkPath.getParent().getFileName().toString().equals(A2Contribution.BOOT)) { + Path base = frameworkPath.getParent().getParent(); + String baseStr = base.toString(); + if (File.separatorChar == '\\')// MS Windows + baseStr = '/' + baseStr.replace(File.separatorChar, '/'); + URI baseUri = new URI(A2Source.SCHEME_A2, null, null, 0, baseStr, null, null); + registerSource(baseUri.toString()); + OsgiBootUtils.debug("Default source from framework location " + frameworkLocation); + return true; + } + } + } catch (Exception e) { + OsgiBootUtils.error("Cannot register default source based on framework location " + frameworkLocation, e); + } + return false; + } + + public void install(String spec) { + if (spec == null) { + for (ProvisioningSource source : sources) { + installWholeSource(source); + } + } + } + + /** @return the new/updated bundle, or null if nothing was done. */ + protected Bundle installOrUpdate(A2Module module) { + try { + ProvisioningSource moduleSource = module.getBranch().getComponent().getContribution().getSource(); + Version moduleVersion = module.getVersion(); + A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion); + if (osgiBranch == null) { + Bundle bundle = moduleSource.install(bc, module); + // TODO make it more dynamic, based on OSGi APIs + osgiContext.registerBundle(bundle); +// if (OsgiBootUtils.isDebug()) +// OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion); + return bundle; + } else { + A2Module lastOsgiModule = osgiBranch.last(); + int compare = moduleVersion.compareTo(lastOsgiModule.getVersion()); + if (compare >= 0) {// update (also if same version) + Bundle bundle = (Bundle) lastOsgiModule.getLocator(); + if (bundle.getBundleId() == 0)// ignore framework bundle + return null; + moduleSource.update(bundle, module); + // TODO make it more dynamic, based on OSGi APIs + // TODO remove old module? Or keep update history? + osgiContext.registerBundle(bundle); + if (compare == 0) + OsgiBootUtils + .debug("Updated bundle " + bundle.getLocation() + " to same version " + moduleVersion); + else + OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); + return bundle; + } else { + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Did not install as bundle module " + module + + " since a module with higher version " + lastOsgiModule.getVersion() + + " is already installed for branch " + osgiBranch); + } + } + } catch (Exception e) { + OsgiBootUtils.error("Could not install module " + module + ": " + e.getMessage(), e); + } + return null; + } + + public Collection update() { + boolean fragmentsUpdated = false; + Set updatedBundles = new HashSet<>(); + bundles: for (Bundle bundle : bc.getBundles()) { + for (ProvisioningSource source : sources) { + String componentId = bundle.getSymbolicName(); + Version version = bundle.getVersion(); + A2Branch branch = source.findBranch(componentId, version); + if (branch == null) + continue bundles; + A2Module module = branch.last(); + Version moduleVersion = module.getVersion(); + int compare = moduleVersion.compareTo(version); + if (compare > 0) {// update + try { + source.update(bundle, module); +// bundle.update(in); + String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST); + if (fragmentHost != null) + fragmentsUpdated = true; + OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); + updatedBundles.add(bundle); + } catch (Exception e) { + OsgiBootUtils.error("Cannot update with module " + module, e); + } + } + } + } + FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); + if (fragmentsUpdated)// refresh all + frameworkWiring.refreshBundles(null); + else + frameworkWiring.refreshBundles(updatedBundles); + return updatedBundles; + } + + private static Map> queryToMap(URI uri) { + return queryToMap(uri.getQuery()); + } + + private static Map> queryToMap(String queryPart) { + try { + final Map> query_pairs = new LinkedHashMap>(); + if (queryPart == null) + return query_pairs; + final String[] pairs = queryPart.split("&"); + for (String pair : pairs) { + final int idx = pair.indexOf("="); + final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name()) + : pair; + if (!query_pairs.containsKey(key)) { + query_pairs.put(key, new LinkedList()); + } + final String value = idx > 0 && pair.length() > idx + 1 + ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) + : null; + query_pairs.get(key).add(value); + } + return query_pairs; + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e); + } + } + +// public static void main(String[] args) { +// if (args.length == 0) +// throw new IllegalArgumentException("Usage: "); +// Map configuration = new HashMap<>(); +// configuration.put("osgi.console", "2323"); +// configuration.put("org.osgi.framework.bootdelegation", +// "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp,sun.nio.cs"); +// Framework framework = OsgiBootUtils.launch(configuration); +// try { +// ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext()); +// Map xOr = new HashMap<>(); +// xOr.put("osgi", "equinox"); +// xOr.put("swt", "rap"); +// FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr); +// context.load(); +// pm.addSource(context); +// if (framework.getBundleContext().getBundles().length == 1) {// initial +// pm.install(null); +// } else { +// pm.update(); +// } +// +// Thread.sleep(2000); +// +// Bundle[] bundles = framework.getBundleContext().getBundles(); +// Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName())); +// for (Bundle b : bundles) +// if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE) +// System.out.println(b.getSymbolicName() + " " + b.getVersion()); +// else +// System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")"); +// } catch (Exception e) { +// e.printStackTrace(); +// } finally { +// try { +// framework.stop(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// } + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java b/org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java new file mode 100644 index 000000000..ddba2a9e7 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java @@ -0,0 +1,21 @@ +package org.argeo.api.a2; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Version; + +/** Where components are retrieved from. */ +public interface ProvisioningSource { + /** List all contributions of this source. */ + Iterable listContributions(Object filter); + + /** Install a module in the OSGi runtime. */ + Bundle install(BundleContext bc, A2Module module); + + /** Update a module in the OSGi runtime. */ + void update(Bundle bundle, A2Module module); + + /** Finds the {@link A2Branch} related to this component and version. */ + A2Branch findBranch(String componentId, Version version); + +} diff --git a/org.argeo.init/src/org/argeo/api/a2/package-info.java b/org.argeo.init/src/org/argeo/api/a2/package-info.java new file mode 100644 index 000000000..6a8bf711b --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/a2/package-info.java @@ -0,0 +1,2 @@ +/** A2 OSGi repository format. */ +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/api/init/RuntimeContext.java b/org.argeo.init/src/org/argeo/api/init/RuntimeContext.java new file mode 100644 index 000000000..9f78d1316 --- /dev/null +++ b/org.argeo.init/src/org/argeo/api/init/RuntimeContext.java @@ -0,0 +1,10 @@ +package org.argeo.api.init; + +/** A runtime context with a life cycle. */ +public interface RuntimeContext extends Runnable { + /** Wait until this runtime context has closed. */ + void waitForStop(long timeout) throws InterruptedException; + + /** Close this runtime context. */ + void close() throws Exception; +} 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/RuntimeContext.java b/org.argeo.init/src/org/argeo/init/RuntimeContext.java deleted file mode 100644 index d83f2ca1c..000000000 --- a/org.argeo.init/src/org/argeo/init/RuntimeContext.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.init; - -/** A runtime context with a life cycle. */ -public interface RuntimeContext extends Runnable { - /** Wait until this runtime context has closed. */ - void waitForStop(long timeout) throws InterruptedException; - - /** Close this runtime context. */ - void close() throws Exception; -} diff --git a/org.argeo.init/src/org/argeo/init/RuntimeManager.java b/org.argeo.init/src/org/argeo/init/RuntimeManager.java deleted file mode 100644 index a5f48d446..000000000 --- a/org.argeo.init/src/org/argeo/init/RuntimeManager.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.argeo.init; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.lang.System.Logger; -import java.lang.System.Logger.Level; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.Properties; -import java.util.TreeMap; -import java.util.function.Consumer; - -import org.argeo.init.logging.ThinLoggerFinder; -import org.argeo.init.osgi.OsgiBoot; -import org.argeo.init.osgi.OsgiRuntimeContext; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; - -public class RuntimeManager { - private final static Logger logger = System.getLogger(RuntimeManager.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 String JVM_ARGS = "jvm.args"; - - private Path baseConfigArea; - private Path baseStateArea; - private Map configuration = new HashMap<>(); - - private Map runtimeContexts = new TreeMap<>(); - - RuntimeManager(Path configArea, Path stateArea) { - loadConfig(configArea, configuration); - configuration.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea.toUri().toString()); - this.baseConfigArea = configArea.getParent(); - this.baseStateArea = stateArea.getParent(); - - logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration); - - } - - public void run() { - logger.log(Level.DEBUG, "Start OSGi"); - try (OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(configuration)) { - runtimeContext.run(); - - BundleContext bc = runtimeContext.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")) { - b.uninstall(); - } - } - bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration)); - logger.log(Level.DEBUG, "Registered runtime manager"); - - runtimeContext.waitForStop(0); - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - - } - - public static void loadConfig(Path dir, Map config) { - try { - System.out.println("Load from "+dir); - Path jvmArgsPath = dir.resolve(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())); - } - } - } catch (IOException e) { - throw new UncheckedIOException("Cannot load configuration from " + dir, e); - } - } - - OsgiRuntimeContext loadRuntime(String relPath, Consumer> configCallback) { - stopRuntime(relPath); - 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()); - - if (configCallback != null) - configCallback.accept(config); - OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(config); - runtimeContexts.put(relPath, runtimeContext); - return runtimeContext; - } - - public void startRuntime(String relPath, Consumer> configCallback) { - OsgiRuntimeContext runtimeContext = loadRuntime(relPath, configCallback); - runtimeContext.run(); - } - - public void stopRuntime(String relPath) { - if (!runtimeContexts.containsKey(relPath)) - return; - RuntimeContext runtimeContext = runtimeContexts.get(relPath); - try { - runtimeContext.close(); - runtimeContext.waitForStop(60 * 1000); - } catch (Exception e) { - logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e); - } finally { - runtimeContexts.remove(relPath); - } - - } - - public static void main(String[] args) { - ThinLoggerFinder.reloadConfiguration(); - 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]); - - // 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)); - - RuntimeManager runtimeManager = new RuntimeManager(configArea, stateArea); - runtimeManager.run(); - } - -} diff --git a/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java new file mode 100644 index 000000000..72b673b68 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java @@ -0,0 +1,203 @@ +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; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +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.OsgiRuntimeContext; +import org.argeo.internal.init.InternalState; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; + +/** + * 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; + private Path baseStateArea; + private Map configuration = new HashMap<>(); + + private Map runtimeContexts = new TreeMap<>(); + + RuntimeManagerMain(Path configArea, Path stateArea) { + loadConfig(configArea, configuration); + configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea.toUri().toString()); + this.baseConfigArea = configArea.getParent(); + this.baseStateArea = stateArea.getParent(); + + logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration); + + } + + public void run() { + OsgiRuntimeContext managerRuntimeContext = new OsgiRuntimeContext(configuration); + try { + managerRuntimeContext.run(); + InternalState.setMainRuntimeContext(managerRuntimeContext); + + // 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(SYMBOLIC_NAME_INIT)) { + b.uninstall(); + } + } + bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration)); + logger.log(Level.DEBUG, "Registered runtime manager"); + + 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); + Path jvmArgsPath = dir.resolve(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())); + } + } + } catch (IOException e) { + throw new UncheckedIOException("Cannot load configuration from " + dir, e); + } + } + + OsgiRuntimeContext loadRuntime(String relPath, Consumer> configCallback) { + stopRuntime(relPath, false); + Path stateArea = baseStateArea.resolve(relPath); + Path configArea = baseConfigArea.resolve(relPath); + Map config = new HashMap<>(); + loadConfig(configArea, config); + 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; + } + + public void startRuntime(String relPath, Consumer> configCallback) { + OsgiRuntimeContext runtimeContext = loadRuntime(relPath, configCallback); + runtimeContext.run(); + } + + public void stopRuntime(String relPath, boolean async) { + if (!runtimeContexts.containsKey(relPath)) + return; + RuntimeContext runtimeContext = runtimeContexts.get(relPath); + try { + runtimeContext.close(); + if (!async) { + runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT); + System.gc(); + } + } catch (Exception e) { + logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e); + } finally { + runtimeContexts.remove(relPath); + } + + } + + public static void main(String[] args) { + ThinLoggerFinder.reloadConfiguration(); + logger.log(Logger.Level.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)); +// } + 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); + runtimeManager.run(); + } + +} diff --git a/org.argeo.init/src/org/argeo/init/Service.java b/org.argeo.init/src/org/argeo/init/Service.java deleted file mode 100644 index b080a7513..000000000 --- a/org.argeo.init/src/org/argeo/init/Service.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.argeo.init; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.System.Logger; -import java.lang.System.Logger.Level; -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.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; -import java.util.TreeMap; - -import org.argeo.init.logging.ThinLoggerFinder; -import org.argeo.init.osgi.OsgiBoot; -import org.argeo.init.osgi.OsgiRuntimeContext; - -/** Configure and launch an Argeo service. */ -public class Service { - private final static Logger logger = System.getLogger(Service.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 List postStart = Collections.synchronizedList(new ArrayList<>()); - - protected Service(String[] args) { - } - - public static void main(String[] args) { - final long pid = ProcessHandle.current().pid(); - logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + pid); - - // 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); - } - } catch (Exception e) { - e.printStackTrace(); - Runtime.getRuntime().halt(1); - } - }, "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); - - if (configArea != null) { - Path configAreaPath = Paths.get(configArea); - Path additionalSystemPropertiesPath = configAreaPath.resolve(FILE_SYSTEM_PROPERTIES); - if (Files.exists(additionalSystemPropertiesPath)) { - Properties properties = new Properties(); - try (InputStream in = Files.newInputStream(additionalSystemPropertiesPath)) { - properties.load(in); - } catch (IOException e) { - logger.log(Logger.Level.ERROR, - "Cannot load additional system properties " + additionalSystemPropertiesPath, e); - } - - for (Object key : properties.keySet()) { - String currentValue = System.getProperty(key.toString()); - String value = properties.getProperty(key.toString()); - if (currentValue != null) { - if (!Objects.equals(value, currentValue)) - logger.log(Logger.Level.WARNING, "System property " + key + " already set with value " - + currentValue + " instead of " + value + ". Ignoring new value."); - } else { - System.setProperty(key.toString(), value); - logger.log(Logger.Level.TRACE, () -> "Added " + key + "=" + value - + " to system properties, from " + additionalSystemPropertiesPath.getFileName()); - } - } - ThinLoggerFinder.reloadConfiguration(); - } - } - - Map config = new HashMap<>(); - config.put(PROP_ARGEO_INIT_MAIN, "true"); - - // add OSGi system properties to the configuration - 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: - // we should already have dealt with those - continue sysprops; - default: - } - - if (keyStr.startsWith("osgi.") || keyStr.startsWith("org.osgi.") || keyStr.startsWith("eclipse.") - || keyStr.startsWith("org.eclipse.equinox.") || keyStr.startsWith("felix.")) { - String value = System.getProperty(keyStr); - config.put(keyStr, value); - logger.log(Logger.Level.TRACE, - () -> "Added " + key + "=" + value + " to configuration, from system properties"); - } - } - - try { - try { - if (stateArea != null) - config.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea); - if (configArea != null) - config.put(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea); - if (dataArea != null) - config.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, dataArea); - // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true"); - - OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config); - osgiRuntimeContext.run(); - Service.runtimeContext = osgiRuntimeContext; - for (Runnable run : postStart) { - try { - run.run(); - } catch (Exception e) { - logger.log(Level.ERROR, "Cannot run post start callback " + run, e); - } - } - Service.runtimeContext.waitForStop(0); - } catch (NoClassDefFoundError noClassDefFoundE) { - StaticRuntimeContext staticRuntimeContext = new StaticRuntimeContext((Map) config); - staticRuntimeContext.run(); - Service.runtimeContext = staticRuntimeContext; - for (Runnable run : postStart) { - try { - run.run(); - } catch (Exception e) { - logger.log(Level.ERROR, "Cannot run post start callback " + run, e); - } - } - Service.runtimeContext.waitForStop(0); - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - 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/ServiceMain.java b/org.argeo.init/src/org/argeo/init/ServiceMain.java new file mode 100644 index 000000000..ca8ba3a7f --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/ServiceMain.java @@ -0,0 +1,159 @@ +package org.argeo.init; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +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.HashMap; +import java.util.List; +import java.util.Map; +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.OsgiRuntimeContext; +import org.argeo.internal.init.InternalState; + +/** 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 List postStart = Collections.synchronizedList(new ArrayList<>()); + + protected ServiceMain(String[] args) { + } + + public static void main(String[] args) { + final long pid = ProcessHandle.current().pid(); + logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + pid); + + // shutdown on exit + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + if (InternalState.getMainRuntimeContext() != null) { + InternalState.getMainRuntimeContext().close(); + InternalState.getMainRuntimeContext().waitForStop(0); + } + } catch (Exception e) { + e.printStackTrace(); + Runtime.getRuntime().halt(1); + } + }, "Runtime shutdown")); + + // TODO use args as well + 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); + Path additionalSystemPropertiesPath = configAreaPath.resolve(FILE_SYSTEM_PROPERTIES); + if (Files.exists(additionalSystemPropertiesPath)) { + Properties properties = new Properties(); + try (InputStream in = Files.newInputStream(additionalSystemPropertiesPath)) { + properties.load(in); + } catch (IOException e) { + logger.log(Logger.Level.ERROR, + "Cannot load additional system properties " + additionalSystemPropertiesPath, e); + } + + for (Object key : properties.keySet()) { + String currentValue = System.getProperty(key.toString()); + String value = properties.getProperty(key.toString()); + if (currentValue != null) { + if (!Objects.equals(value, currentValue)) + logger.log(Logger.Level.WARNING, "System property " + key + " already set with value " + + currentValue + " instead of " + value + ". Ignoring new value."); + } else { + System.setProperty(key.toString(), value); + logger.log(Logger.Level.TRACE, () -> "Added " + key + "=" + value + + " to system properties, from " + additionalSystemPropertiesPath.getFileName()); + } + } + ThinLoggerFinder.reloadConfiguration(); + } + } + + Map config = new HashMap<>(); + config.put(PROP_ARGEO_INIT_MAIN, "true"); + + // add OSGi system properties to the configuration + sysprops: for (Object key : new TreeMap<>(System.getProperties()).keySet()) { + String keyStr = key.toString(); + switch (keyStr) { + 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: + } + + if (keyStr.startsWith("osgi.") || keyStr.startsWith("org.osgi.") || keyStr.startsWith("eclipse.") + || keyStr.startsWith("org.eclipse.equinox.") || keyStr.startsWith("felix.")) { + String value = System.getProperty(keyStr); + config.put(keyStr, value); + logger.log(Logger.Level.TRACE, + () -> "Added " + key + "=" + value + " to configuration, from system properties"); + } + } + + try { + try { + if (stateArea != null) + config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea); + if (configArea != null) + config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea); + if (dataArea != null) + config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, dataArea); + // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true"); + + OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config); + osgiRuntimeContext.run(); + InternalState.setMainRuntimeContext(osgiRuntimeContext); + for (Runnable run : postStart) { + try { + run.run(); + } catch (Exception e) { + logger.log(Level.ERROR, "Cannot run post start callback " + run, e); + } + } + InternalState.getMainRuntimeContext().waitForStop(0); + } catch (NoClassDefFoundError noClassDefFoundE) { + StaticRuntimeContext staticRuntimeContext = new StaticRuntimeContext((Map) config); + staticRuntimeContext.run(); + InternalState.setMainRuntimeContext(staticRuntimeContext); + for (Runnable run : postStart) { + try { + run.run(); + } catch (Exception e) { + logger.log(Level.ERROR, "Cannot run post start callback " + run, e); + } + } + InternalState.getMainRuntimeContext().waitForStop(0); + } + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + logger.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid); + } + + /** 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/a2/A2Branch.java b/org.argeo.init/src/org/argeo/init/a2/A2Branch.java deleted file mode 100644 index cd8d89541..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/A2Branch.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.argeo.init.a2; - -import java.util.Collections; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.argeo.init.osgi.OsgiBootUtils; -import org.osgi.framework.Version; - -/** - * A logical linear sequence of versions of a given {@link A2Component}. This is - * typically a combination of major and minor version, indicating backward - * compatibility. - */ -public class A2Branch implements Comparable { - private final A2Component component; - private final String id; - - final SortedMap modules = Collections.synchronizedSortedMap(new TreeMap<>()); - - public A2Branch(A2Component component, String id) { - this.component = component; - this.id = id; - component.branches.put(id, this); - } - - public Iterable listModules(Object filter) { - return modules.values(); - } - - A2Module getOrAddModule(Version version, Object locator) { - if (modules.containsKey(version)) { - A2Module res = modules.get(version); - if (OsgiBootUtils.isDebug() && !res.getLocator().equals(locator)) { - OsgiBootUtils.debug("Inconsistent locator " + locator + " (registered: " + res.getLocator() + ")"); - } - return res; - } else - return new A2Module(this, version, locator); - } - - public A2Module last() { - return modules.get(modules.lastKey()); - } - - public A2Module first() { - return modules.get(modules.firstKey()); - } - - public A2Component getComponent() { - return component; - } - - public String getId() { - return id; - } - - @Override - public int compareTo(A2Branch o) { - return id.compareTo(id); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof A2Branch) { - A2Branch o = (A2Branch) obj; - return component.equals(o.component) && id.equals(o.id); - } else - return false; - } - - @Override - public String toString() { - return getCoordinates(); - } - - public String getCoordinates() { - return component + ":" + id; - } - - static String versionToBranchId(Version version) { - return version.getMajor() + "." + version.getMinor(); - } -} diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Component.java b/org.argeo.init/src/org/argeo/init/a2/A2Component.java deleted file mode 100644 index 894270630..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/A2Component.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.argeo.init.a2; - -import java.util.Collections; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.osgi.framework.Version; - -/** - * The logical name of a software package. In OSGi's case this is - * Bundle-SymbolicName. This is the equivalent of Maven's artifact - * id. - */ -public class A2Component implements Comparable { - private final A2Contribution contribution; - private final String id; - - final SortedMap branches = Collections.synchronizedSortedMap(new TreeMap<>()); - - public A2Component(A2Contribution contribution, String id) { - this.contribution = contribution; - this.id = id; - contribution.components.put(id, this); - } - - public Iterable listBranches(Object filter) { - return branches.values(); - } - - A2Branch getOrAddBranch(String branchId) { - if (!branches.containsKey(branchId)) { - A2Branch a2Branch = new A2Branch(this, branchId); - branches.put(branchId, a2Branch); - } - return branches.get(branchId); - } - - A2Module getOrAddModule(Version version, Object locator) { - A2Branch branch = getOrAddBranch(A2Branch.versionToBranchId(version)); - A2Module module = branch.getOrAddModule(version, locator); - return module; - } - - public A2Branch last() { - return branches.get(branches.lastKey()); - } - - public A2Contribution getContribution() { - return contribution; - } - - public String getId() { - return id; - } - - @Override - public int compareTo(A2Component o) { - return id.compareTo(o.id); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof A2Component) { - A2Component o = (A2Component) obj; - return contribution.equals(o.contribution) && id.equals(o.id); - } else - return false; - } - - @Override - public String toString() { - return contribution.getId() + ":" + id; - } - - void asTree(String prefix, StringBuffer buf) { - if (prefix == null) - prefix = ""; - A2Branch lastBranch = last(); - SortedMap displayMap = new TreeMap<>(Collections.reverseOrder()); - displayMap.putAll(branches); - for (String branchId : displayMap.keySet()) { - A2Branch branch = displayMap.get(branchId); - if (!lastBranch.equals(branch)) { - buf.append('\n'); - buf.append(prefix); - } else { - buf.append(" -"); - } - buf.append(prefix); - buf.append(branchId); - A2Module first = branch.first(); - A2Module last = branch.last(); - buf.append(" (").append(last.getVersion()); - if (!first.equals(last)) - buf.append(" ... ").append(first.getVersion()); - buf.append(')'); - } - } - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java b/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java deleted file mode 100644 index 9de09cecf..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java +++ /dev/null @@ -1,151 +0,0 @@ -package org.argeo.init.a2; - -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; - -/** - * A category grouping a set of {@link A2Component}, typically based on the - * provider of these components. This is the equivalent of Maven's group Id. - */ -public class A2Contribution implements Comparable { - final static String BOOT = "boot"; - final static String RUNTIME = "runtime"; - final static String CLASSPATH = "classpath"; - - final static String DEFAULT = "default"; - final static String LIB = "lib"; - - private final ProvisioningSource source; - private final String id; - - final Map components = Collections.synchronizedSortedMap(new TreeMap<>()); - - /** - * The contribution must be added to the source. Rather use - * {@link AbstractProvisioningSource#getOrAddContribution(String)} than this - * contructor directly. - */ - public A2Contribution(ProvisioningSource context, String id) { - this.source = context; - this.id = id; -// if (context != null) -// context.contributions.put(id, this); - } - - public Iterable listComponents(Object filter) { - return components.values(); - } - - A2Component getOrAddComponent(String componentId) { - if (components.containsKey(componentId)) - return components.get(componentId); - else - return new A2Component(this, componentId); - } - - public ProvisioningSource getSource() { - return source; - } - - public String getId() { - return id; - } - - @Override - public int compareTo(A2Contribution o) { - return id.compareTo(o.id); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof A2Contribution) { - A2Contribution o = (A2Contribution) obj; - return id.equals(o.id); - } else - return false; - } - - @Override - public String toString() { - return id; - } - - void asTree(String prefix, StringBuffer buf) { - if (prefix == null) - prefix = ""; - for (String componentId : components.keySet()) { - buf.append(prefix); - buf.append(componentId); - A2Component component = components.get(componentId); - component.asTree(prefix, buf); - buf.append('\n'); - } - } - - static String localOsArchRelativePath() { - return Os.local().toString() + "/" + Arch.local().toString(); - } - - static enum Os { - LINUX, WIN32, MACOSX, UNKOWN; - - @Override - public String toString() { - return name().toLowerCase(); - } - - public static Os local() { - String osStr = System.getProperty("os.name").toLowerCase(); - if (osStr.startsWith("linux")) - return LINUX; - if (osStr.startsWith("win")) - return WIN32; - if (osStr.startsWith("mac")) - return MACOSX; - return UNKOWN; - } - - } - - static enum Arch { - X86_64, AARCH64, X86, POWERPC, UNKOWN; - - @Override - public String toString() { - return name().toLowerCase(); - } - - public static Arch local() { - String archStr = System.getProperty("os.arch").toLowerCase(); - return switch (archStr) { - case "x86_64": - case "amd64": - case "x86-64": { - yield X86_64; - } - case "aarch64": - case "arm64": { - yield AARCH64; - } - case "x86": - case "i386": - case "i686": { - yield X86; - } - case "powerpc": - case "ppc": { - yield POWERPC; - } - default: - yield UNKOWN; - }; - } - } - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Exception.java b/org.argeo.init/src/org/argeo/init/a2/A2Exception.java deleted file mode 100644 index 6ba87a7d0..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/A2Exception.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.init.a2; - -/** Unchecked A2 provisioning exception. */ -public class A2Exception extends RuntimeException { - private static final long serialVersionUID = 1927603558545397360L; - - public A2Exception(String message, Throwable e) { - super(message, e); - } - - public A2Exception(String message) { - super(message); - } - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Module.java b/org.argeo.init/src/org/argeo/init/a2/A2Module.java deleted file mode 100644 index 0b6d3a91c..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/A2Module.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.argeo.init.a2; - -import org.osgi.framework.Version; - -/** - * An identified software package. In OSGi's case this is the combination of - * Bundle-SymbolicName and Bundle-version. This is the - * equivalent of the full coordinates of a Maven artifact version. - */ -public class A2Module implements Comparable { - private final A2Branch branch; - private final Version version; - private final Object locator; - - public A2Module(A2Branch branch, Version version, Object locator) { - this.branch = branch; - this.version = version; - this.locator = locator; - branch.modules.put(version, this); - } - - public A2Branch getBranch() { - return branch; - } - - public Version getVersion() { - return version; - } - - Object getLocator() { - return locator; - } - - @Override - public int compareTo(A2Module o) { - return version.compareTo(o.version); - } - - @Override - public int hashCode() { - return version.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof A2Module) { - A2Module o = (A2Module) obj; - return branch.equals(o.branch) && version.equals(o.version); - } else - return false; - } - - @Override - public String toString() { - return getCoordinates(); - } - - public String getCoordinates() { - return branch.getComponent() + ":" + version; - } - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Source.java b/org.argeo.init/src/org/argeo/init/a2/A2Source.java deleted file mode 100644 index 19134b784..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/A2Source.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.init.a2; - -import java.net.URI; - -/** A provisioning source in A2 format. */ -public interface A2Source extends ProvisioningSource { - /** Use standard a2 protocol, installing from source URL. */ - final static String SCHEME_A2 = "a2"; - /** - * Use equinox-specific reference: installation, which does not copy the bundle - * content. - */ - final static String SCHEME_A2_REFERENCE = "a2+reference"; - final static String DEFAULT_A2_URI = SCHEME_A2 + ":///"; - final static String DEFAULT_A2_REFERENCE_URI = SCHEME_A2_REFERENCE + ":///"; - - final static String INCLUDE = "include"; - final static String EXCLUDE = "exclude"; - - URI getUri(); -} diff --git a/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java b/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java deleted file mode 100644 index f946add69..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java +++ /dev/null @@ -1,269 +0,0 @@ -package org.argeo.init.a2; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Collections; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; -import org.osgi.framework.Constants; -import org.osgi.framework.Version; - -/** Where components are retrieved from. */ -public abstract class AbstractProvisioningSource implements ProvisioningSource { - protected final Map contributions = Collections.synchronizedSortedMap(new TreeMap<>()); - - private final boolean usingReference; - - public AbstractProvisioningSource(boolean usingReference) { - this.usingReference = usingReference; - } - - public Iterable listContributions(Object filter) { - return contributions.values(); - } - - @Override - public Bundle install(BundleContext bc, A2Module module) { - try { - Object locator = module.getLocator(); - if (usingReference && locator instanceof Path locatorPath) { - String referenceUrl = "reference:file:" + locatorPath.toString(); - Bundle bundle = bc.installBundle(referenceUrl); - return bundle; - } else { - Path locatorPath = (Path) locator; - Path pathToUse; - boolean isTemp = false; - if (locator instanceof Path && Files.isDirectory(locatorPath)) { - pathToUse = toTempJar(locatorPath); - isTemp = true; - } else { - pathToUse = locatorPath; - } - Bundle bundle; - try (InputStream in = newInputStream(pathToUse)) { - bundle = bc.installBundle(locatorPath.toAbsolutePath().toString(), in); - } - - if (isTemp && pathToUse != null) - Files.deleteIfExists(pathToUse); - return bundle; - } - } catch (BundleException | IOException e) { - throw new A2Exception("Cannot install module " + module, e); - } - } - - @Override - public void update(Bundle bundle, A2Module module) { - try { - Object locator = module.getLocator(); - if (usingReference && locator instanceof Path) { - try (InputStream in = newInputStream(locator)) { - bundle.update(in); - } - } else { - Path locatorPath = (Path) locator; - Path pathToUse; - boolean isTemp = false; - if (locator instanceof Path && Files.isDirectory(locatorPath)) { - pathToUse = toTempJar(locatorPath); - isTemp = true; - } else { - pathToUse = locatorPath; - } - try (InputStream in = newInputStream(pathToUse)) { - bundle.update(in); - } - if (isTemp && pathToUse != null) - Files.deleteIfExists(pathToUse); - } - } catch (BundleException | IOException e) { - throw new A2Exception("Cannot update module " + module, e); - } - } - - @Override - public A2Branch findBranch(String componentId, Version version) { - A2Component component = findComponent(componentId); - if (component == null) - return null; - String branchId = version.getMajor() + "." + version.getMinor(); - if (!component.branches.containsKey(branchId)) - return null; - return component.branches.get(branchId); - } - - protected A2Contribution getOrAddContribution(String contributionId) { - if (contributions.containsKey(contributionId)) - return contributions.get(contributionId); - else { - A2Contribution contribution = new A2Contribution(this, contributionId); - contributions.put(contributionId, contribution); - return contribution; - } - } - - protected void asTree(String prefix, StringBuffer buf) { - if (prefix == null) - prefix = ""; - for (String contributionId : contributions.keySet()) { - buf.append(prefix); - buf.append(contributionId); - buf.append('\n'); - A2Contribution contribution = contributions.get(contributionId); - contribution.asTree(prefix + " ", buf); - } - } - - protected void asTree() { - StringBuffer buf = new StringBuffer(); - asTree("", buf); - System.out.println(buf); - } - - protected A2Component findComponent(String componentId) { - SortedMap res = new TreeMap<>(); - for (A2Contribution contribution : contributions.values()) { - components: for (String componentIdKey : contribution.components.keySet()) { - if (componentId.equals(componentIdKey)) { - res.put(contribution, contribution.components.get(componentIdKey)); - break components; - } - } - } - if (res.size() == 0) - return null; - // TODO explicit contribution priorities - return res.get(res.lastKey()); - - } - - protected String[] readNameVersionFromModule(Path modulePath) { - Manifest manifest; - if (Files.isDirectory(modulePath)) { - manifest = findManifest(modulePath); - } else { - try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) { - manifest = in.getManifest(); - } catch (IOException e) { - throw new A2Exception("Cannot read manifest from " + modulePath, e); - } - } - String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION); - String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); - int semiColIndex = symbolicName.indexOf(';'); - if (semiColIndex >= 0) - symbolicName = symbolicName.substring(0, semiColIndex); - return new String[] { symbolicName, versionStr }; - } - - protected String readVersionFromModule(Path modulePath) { - Manifest manifest; - if (Files.isDirectory(modulePath)) { - manifest = findManifest(modulePath); - } else { - try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) { - manifest = in.getManifest(); - } catch (IOException e) { - throw new A2Exception("Cannot read manifest from " + modulePath, e); - } - } - String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION); - return versionStr; - } - - protected String readSymbolicNameFromModule(Path modulePath) { - Manifest manifest; - if (Files.isDirectory(modulePath)) { - manifest = findManifest(modulePath); - } else { - try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) { - manifest = in.getManifest(); - } catch (IOException e) { - throw new A2Exception("Cannot read manifest from " + modulePath, e); - } - } - String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); - int semiColIndex = symbolicName.indexOf(';'); - if (semiColIndex >= 0) - symbolicName = symbolicName.substring(0, semiColIndex); - return symbolicName; - } - - protected boolean isUsingReference() { - return usingReference; - } - - private InputStream newInputStream(Object locator) throws IOException { - if (locator instanceof Path) { - return Files.newInputStream((Path) locator); - } else if (locator instanceof URL) { - return ((URL) locator).openStream(); - } else { - throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass()); - } - } - - private static Manifest findManifest(Path currentPath) { - Path metaInfPath = currentPath.resolve("META-INF"); - if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) { - Path manifestPath = metaInfPath.resolve("MANIFEST.MF"); - try { - try (InputStream in = Files.newInputStream(manifestPath)) { - Manifest manifest = new Manifest(in); - return manifest; - } - } catch (IOException e) { - throw new A2Exception("Cannot read manifest from " + manifestPath, e); - } - } else { - Path parentPath = currentPath.getParent(); - if (parentPath == null) - throw new A2Exception("MANIFEST.MF file not found."); - return findManifest(currentPath.getParent()); - } - } - - private static Path toTempJar(Path dir) { - try { - Manifest manifest = findManifest(dir); - Path jarPath = Files.createTempFile("a2Source", ".jar"); - try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) { - Files.walkFileTree(dir, new SimpleFileVisitor() { - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Path relPath = dir.relativize(file); - // skip MANIFEST from folder - if (relPath.toString().contentEquals("META-INF/MANIFEST.MF")) - return FileVisitResult.CONTINUE; - zos.putNextEntry(new ZipEntry(relPath.toString())); - Files.copy(file, zos); - zos.closeEntry(); - return FileVisitResult.CONTINUE; - } - }); - } - return jarPath; - } catch (IOException e) { - throw new A2Exception("Cannot install OSGi bundle from " + dir, e); - } - - } - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java b/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java deleted file mode 100644 index 12de4228b..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.argeo.init.a2; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; - -import org.argeo.init.osgi.OsgiBootUtils; -import org.osgi.framework.Version; - -/** - * A provisioning source based on the linear classpath with which the JVM has - * been started. - */ -public class ClasspathSource extends AbstractProvisioningSource { - - public ClasspathSource() { - super(true); - } - - void load() throws IOException { - A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH); - List classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator)); - parts: for (String part : classpath) { - Path file = Paths.get(part); - Version version; - try { - version = new Version(readVersionFromModule(file)); - } catch (Exception e) { - // ignore non OSGi - continue parts; - } - String moduleName = readSymbolicNameFromModule(file); - A2Component component = classpathContribution.getOrAddComponent(moduleName); - A2Module module = component.getOrAddModule(version, file); - if (OsgiBootUtils.isDebug()) - OsgiBootUtils.debug("Registered " + module); - } - - } -} diff --git a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java deleted file mode 100644 index acc553d7d..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.argeo.init.a2; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.StringJoiner; -import java.util.TreeMap; - -import org.argeo.init.osgi.OsgiBootUtils; -import org.osgi.framework.Version; - -/** A file system {@link AbstractProvisioningSource} in A2 format. */ -public class FsA2Source extends AbstractProvisioningSource implements A2Source { - private final Path base; - private final Map variantsXOr; - - private final List includes; - private final List excludes; - - public FsA2Source(Path base, Map variantsXOr, boolean usingReference, List includes, - List excludes) { - super(usingReference); - this.base = base; - this.variantsXOr = new HashMap<>(variantsXOr); - this.includes = includes; - this.excludes = excludes; - } - - void load() throws IOException { - SortedMap contributions = new TreeMap<>(); - - DirectoryStream contributionPaths = Files.newDirectoryStream(base); - contributions: for (Path contributionPath : contributionPaths) { - if (Files.isDirectory(contributionPath)) { - String contributionId = contributionPath.getFileName().toString(); - if (A2Contribution.BOOT.equals(contributionId))// skip boot - continue contributions; - if (contributionId.contains(".")) { - A2Contribution contribution = getOrAddContribution(contributionId); - contributions.put(contributionPath, contribution); - } else {// variants - Path variantPath = null; - // is it an explicit variant? - String variant = variantsXOr.get(contributionPath.getFileName().toString()); - if (variant != null) { - variantPath = contributionPath.resolve(variant); - } - - // is there a default variant? - if (variantPath == null) { - Path defaultPath = contributionPath.resolve(A2Contribution.DEFAULT); - if (Files.exists(defaultPath)) { - variantPath = defaultPath; - } - } - - if (variantPath == null) - continue contributions; - - // a variant was found, let's collect its contributions (also common ones in its - // parent) - if (Files.exists(variantPath.getParent())) { - for (Path variantContributionPath : Files.newDirectoryStream(variantPath.getParent())) { - String variantContributionId = variantContributionPath.getFileName().toString(); - if (variantContributionId.contains(".")) { - A2Contribution contribution = getOrAddContribution(variantContributionId); - contributions.put(variantContributionPath, contribution); - } - } - } - if (Files.exists(variantPath)) { - for (Path variantContributionPath : Files.newDirectoryStream(variantPath)) { - String variantContributionId = variantContributionPath.getFileName().toString(); - if (variantContributionId.contains(".")) { - A2Contribution contribution = getOrAddContribution(variantContributionId); - contributions.put(variantContributionPath, contribution); - } - } - } - } - } - } - - contributions: for (Path contributionPath : contributions.keySet()) { - String contributionId = contributionPath.getFileName().toString(); - if (includes != null && !includes.contains(contributionId)) - continue contributions; - if (excludes != null && excludes.contains(contributionId)) - continue contributions; - A2Contribution contribution = getOrAddContribution(contributionId); - DirectoryStream modulePaths = Files.newDirectoryStream(contributionPath); - modules: for (Path modulePath : modulePaths) { - if (!Files.isDirectory(modulePath)) { - // OsgiBootUtils.debug("Registering " + modulePath); - String moduleFileName = modulePath.getFileName().toString(); - int lastDot = moduleFileName.lastIndexOf('.'); - String ext = moduleFileName.substring(lastDot + 1); - if (!"jar".equals(ext)) - continue modules; - Version version; - // TODO optimise? check attributes? - String[] nameVersion = readNameVersionFromModule(modulePath); - String componentName = nameVersion[0]; - String versionStr = nameVersion[1]; - if (versionStr != null) { - version = new Version(versionStr); - } else { - OsgiBootUtils.debug("Ignore " + modulePath + " since version cannot be found"); - continue modules; - } -// } - A2Component component = contribution.getOrAddComponent(componentName); - A2Module module = component.getOrAddModule(version, modulePath); - if (OsgiBootUtils.isDebug()) - OsgiBootUtils.debug("Registered " + module); - } - } - } - - } - - @Override - public URI getUri() { - URI baseUri = base.toUri(); - try { - if (baseUri.getScheme().equals("file")) { - String queryPart = ""; - if (!getVariantsXOr().isEmpty()) { - StringJoiner sj = new StringJoiner("&"); - for (String key : getVariantsXOr().keySet()) { - sj.add(key + "=" + getVariantsXOr().get(key)); - } - queryPart = sj.toString(); - } - return new URI(isUsingReference() ? SCHEME_A2_REFERENCE : SCHEME_A2, null, base.toString(), queryPart, - null); - } else { - throw new UnsupportedOperationException("Unsupported scheme " + baseUri.getScheme()); - } - } catch (URISyntaxException e) { - throw new IllegalStateException("Cannot build URI from " + baseUri, e); - } - } - - protected Map getVariantsXOr() { - return variantsXOr; - } - -// public static void main(String[] args) { -// if (args.length == 0) -// throw new IllegalArgumentException("Usage: "); -// try { -// Map xOr = new HashMap<>(); -// xOr.put("osgi", "equinox"); -// xOr.put("swt", "rap"); -// FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr); -// context.load(); -// context.asTree(); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java deleted file mode 100644 index 0313d20f3..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.argeo.init.a2; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; - -import org.argeo.init.osgi.OsgiBootUtils; -import org.osgi.framework.Version; - -/** A file system {@link AbstractProvisioningSource} in Maven 2 format. */ -public class FsM2Source extends AbstractProvisioningSource { - private final Path base; - - public FsM2Source(Path base) { - super(false); - this.base = base; - } - - void load() throws IOException { - Files.walkFileTree(base, new ArtifactFileVisitor()); - } - - class ArtifactFileVisitor extends SimpleFileVisitor { - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - // OsgiBootUtils.debug("Processing " + file); - if (file.toString().endsWith(".jar")) { - Version version; - try { - version = new Version(readVersionFromModule(file)); - } catch (Exception e) { - // ignore non OSGi - return FileVisitResult.CONTINUE; - } - String moduleName = readSymbolicNameFromModule(file); - Path groupPath = file.getParent().getParent().getParent(); - Path relGroupPath = base.relativize(groupPath); - String contributionName = relGroupPath.toString().replace(File.separatorChar, '.'); - A2Contribution contribution = getOrAddContribution(contributionName); - A2Component component = contribution.getOrAddComponent(moduleName); - A2Module module = component.getOrAddModule(version, file); - if (OsgiBootUtils.isDebug()) - OsgiBootUtils.debug("Registered " + module); - } - return super.visitFile(file, attrs); - } - - } - - public static void main(String[] args) { - try { - FsM2Source context = new FsM2Source(Paths.get("/home/mbaudier/.m2/repository")); - context.load(); - context.asTree(); - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java b/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java deleted file mode 100644 index 7f1133f67..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.init.a2; - -import org.argeo.init.osgi.OsgiBootUtils; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.Version; - -/** - * A running OSGi bundle context seen as a {@link AbstractProvisioningSource}. - */ -class OsgiContext extends AbstractProvisioningSource { - private final BundleContext bc; - - private A2Contribution runtimeContribution; - - public OsgiContext(BundleContext bc) { - super(false); - this.bc = bc; - runtimeContribution = getOrAddContribution(A2Contribution.RUNTIME); - } - - public OsgiContext() { - super(false); - Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class); - if (bundle == null) - throw new IllegalArgumentException( - "OSGi Boot bundle must be started or a bundle context must be specified"); - this.bc = bundle.getBundleContext(); - } - - void load() { - for (Bundle bundle : bc.getBundles()) { - registerBundle(bundle); - } - - } - - void registerBundle(Bundle bundle) { - String componentId = bundle.getSymbolicName(); - Version version = bundle.getVersion(); - A2Component component = runtimeContribution.getOrAddComponent(componentId); - A2Module module = component.getOrAddModule(version, bundle); - if (OsgiBootUtils.isDebug()) - OsgiBootUtils.debug("Registered bundle module " + module + " (location id: " + bundle.getLocation() + ")"); - - } -} diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java deleted file mode 100644 index 80006264c..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java +++ /dev/null @@ -1,303 +0,0 @@ -package org.argeo.init.a2; - -import static org.argeo.init.a2.A2Source.SCHEME_A2; -import static org.argeo.init.a2.A2Source.SCHEME_A2_REFERENCE; - -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.argeo.init.osgi.OsgiBootUtils; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.Version; -import org.osgi.framework.wiring.FrameworkWiring; - -/** Loads provisioning sources into an OSGi context. */ -public class ProvisioningManager { - BundleContext bc; - OsgiContext osgiContext; - List sources = Collections.synchronizedList(new ArrayList<>()); - - public ProvisioningManager(BundleContext bc) { - this.bc = bc; - osgiContext = new OsgiContext(bc); - osgiContext.load(); - } - - protected void addSource(ProvisioningSource source) { - sources.add(source); - } - - void installWholeSource(ProvisioningSource source) { - Set updatedBundles = new HashSet<>(); - for (A2Contribution contribution : source.listContributions(null)) { - for (A2Component component : contribution.components.values()) { - A2Module module = component.last().last(); - Bundle bundle = installOrUpdate(module); - if (bundle != null) - updatedBundles.add(bundle); - } - } -// FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); -// frameworkWiring.refreshBundles(updatedBundles); - } - - public void registerSource(String uri) { - try { - URI u = new URI(uri); - - // XOR - Map> properties = queryToMap(u); - Map xOr = new HashMap<>(); - List includes = null; - List excludes = null; - for (String key : properties.keySet()) { - List lst = properties.get(key); - if (A2Source.INCLUDE.equals(key)) { - includes = new ArrayList<>(lst); - } else if (A2Source.EXCLUDE.equals(key)) { - excludes = new ArrayList<>(lst); - } else { - if (lst.size() != 1) - throw new IllegalArgumentException("Invalid XOR definitions in " + uri); - xOr.put(key, lst.get(0)); - } - } - - if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) { - if (u.getHost() == null || "".equals(u.getHost())) { - String baseStr = u.getPath(); - if (File.separatorChar == '\\') {// MS Windows - baseStr = baseStr.substring(1).replace('/', File.separatorChar); - } - Path base = Paths.get(baseStr); - if (Files.exists(base)) { - FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()), - includes, excludes); - source.load(); - addSource(source); - OsgiBootUtils.info("Registered " + uri + " as source"); - - // OS specific / native - String localRelPath = A2Contribution.localOsArchRelativePath(); - Path localLibBase = base.resolve(A2Contribution.LIB).resolve(localRelPath); - if (Files.exists(localLibBase)) { - FsA2Source libSource = new FsA2Source(localLibBase, xOr, - SCHEME_A2_REFERENCE.equals(u.getScheme()), includes, excludes); - libSource.load(); - addSource(libSource); - OsgiBootUtils.info("Registered OS-specific " + uri + " as source (" + localRelPath + ")"); - } - } else { - OsgiBootUtils.debug("Source " + base + " does not exist, ignoring."); - } - } else { - throw new UnsupportedOperationException( - "Remote installation is not yet supported, cannot add source " + u); - } - } else { - throw new IllegalArgumentException("Unkown scheme: for source " + u); - } - } catch (Exception e) { - throw new A2Exception("Cannot add source " + uri, e); - } - } - - public boolean registerDefaultSource() { - String frameworkLocation = bc.getProperty("osgi.framework"); - try { - URI frameworkLocationUri = new URI(frameworkLocation); - if ("file".equals(frameworkLocationUri.getScheme())) { - Path frameworkPath = Paths.get(frameworkLocationUri); - if (frameworkPath.getParent().getFileName().toString().equals(A2Contribution.BOOT)) { - Path base = frameworkPath.getParent().getParent(); - String baseStr = base.toString(); - if (File.separatorChar == '\\')// MS Windows - baseStr = '/' + baseStr.replace(File.separatorChar, '/'); - URI baseUri = new URI(A2Source.SCHEME_A2, null, null, 0, baseStr, null, null); - registerSource(baseUri.toString()); - OsgiBootUtils.debug("Default source from framework location " + frameworkLocation); - return true; - } - } - } catch (Exception e) { - OsgiBootUtils.error("Cannot register default source based on framework location " + frameworkLocation, e); - } - return false; - } - - public void install(String spec) { - if (spec == null) { - for (ProvisioningSource source : sources) { - installWholeSource(source); - } - } - } - - /** @return the new/updated bundle, or null if nothing was done. */ - protected Bundle installOrUpdate(A2Module module) { - try { - ProvisioningSource moduleSource = module.getBranch().getComponent().getContribution().getSource(); - Version moduleVersion = module.getVersion(); - A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion); - if (osgiBranch == null) { - Bundle bundle = moduleSource.install(bc, module); - // TODO make it more dynamic, based on OSGi APIs - osgiContext.registerBundle(bundle); -// if (OsgiBootUtils.isDebug()) -// OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion); - return bundle; - } else { - A2Module lastOsgiModule = osgiBranch.last(); - int compare = moduleVersion.compareTo(lastOsgiModule.getVersion()); - if (compare >= 0) {// update (also if same version) - Bundle bundle = (Bundle) lastOsgiModule.getLocator(); - if (bundle.getBundleId() == 0)// ignore framework bundle - return null; - moduleSource.update(bundle, module); - // TODO make it more dynamic, based on OSGi APIs - // TODO remove old module? Or keep update history? - osgiContext.registerBundle(bundle); - if (compare == 0) - OsgiBootUtils - .debug("Updated bundle " + bundle.getLocation() + " to same version " + moduleVersion); - else - OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); - return bundle; - } else { - if (OsgiBootUtils.isDebug()) - OsgiBootUtils.debug("Did not install as bundle module " + module - + " since a module with higher version " + lastOsgiModule.getVersion() - + " is already installed for branch " + osgiBranch); - } - } - } catch (Exception e) { - OsgiBootUtils.error("Could not install module " + module + ": " + e.getMessage(), e); - } - return null; - } - - public Collection update() { - boolean fragmentsUpdated = false; - Set updatedBundles = new HashSet<>(); - bundles: for (Bundle bundle : bc.getBundles()) { - for (ProvisioningSource source : sources) { - String componentId = bundle.getSymbolicName(); - Version version = bundle.getVersion(); - A2Branch branch = source.findBranch(componentId, version); - if (branch == null) - continue bundles; - A2Module module = branch.last(); - Version moduleVersion = module.getVersion(); - int compare = moduleVersion.compareTo(version); - if (compare > 0) {// update - try { - source.update(bundle, module); -// bundle.update(in); - String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST); - if (fragmentHost != null) - fragmentsUpdated = true; - OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); - updatedBundles.add(bundle); - } catch (Exception e) { - OsgiBootUtils.error("Cannot update with module " + module, e); - } - } - } - } - FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); - if (fragmentsUpdated)// refresh all - frameworkWiring.refreshBundles(null); - else - frameworkWiring.refreshBundles(updatedBundles); - return updatedBundles; - } - - private static Map> queryToMap(URI uri) { - return queryToMap(uri.getQuery()); - } - - private static Map> queryToMap(String queryPart) { - try { - final Map> query_pairs = new LinkedHashMap>(); - if (queryPart == null) - return query_pairs; - final String[] pairs = queryPart.split("&"); - for (String pair : pairs) { - final int idx = pair.indexOf("="); - final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name()) - : pair; - if (!query_pairs.containsKey(key)) { - query_pairs.put(key, new LinkedList()); - } - final String value = idx > 0 && pair.length() > idx + 1 - ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) - : null; - query_pairs.get(key).add(value); - } - return query_pairs; - } catch (UnsupportedEncodingException e) { - throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e); - } - } - -// public static void main(String[] args) { -// if (args.length == 0) -// throw new IllegalArgumentException("Usage: "); -// Map configuration = new HashMap<>(); -// configuration.put("osgi.console", "2323"); -// configuration.put("org.osgi.framework.bootdelegation", -// "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp,sun.nio.cs"); -// Framework framework = OsgiBootUtils.launch(configuration); -// try { -// ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext()); -// Map xOr = new HashMap<>(); -// xOr.put("osgi", "equinox"); -// xOr.put("swt", "rap"); -// FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr); -// context.load(); -// pm.addSource(context); -// if (framework.getBundleContext().getBundles().length == 1) {// initial -// pm.install(null); -// } else { -// pm.update(); -// } -// -// Thread.sleep(2000); -// -// Bundle[] bundles = framework.getBundleContext().getBundles(); -// Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName())); -// for (Bundle b : bundles) -// if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE) -// System.out.println(b.getSymbolicName() + " " + b.getVersion()); -// else -// System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")"); -// } catch (Exception e) { -// e.printStackTrace(); -// } finally { -// try { -// framework.stop(); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } -// } - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java b/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java deleted file mode 100644 index 99356300e..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.init.a2; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Version; - -/** Where components are retrieved from. */ -public interface ProvisioningSource { - /** List all contributions of this source. */ - Iterable listContributions(Object filter); - - /** Install a module in the OSGi runtime. */ - Bundle install(BundleContext bc, A2Module module); - - /** Update a module in the OSGi runtime. */ - void update(Bundle bundle, A2Module module); - - /** Finds the {@link A2Branch} related to this component and version. */ - A2Branch findBranch(String componentId, Version version); - -} diff --git a/org.argeo.init/src/org/argeo/init/a2/package-info.java b/org.argeo.init/src/org/argeo/init/a2/package-info.java deleted file mode 100644 index bb8fa6e00..000000000 --- a/org.argeo.init/src/org/argeo/init/a2/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** A2 OSGi repository format. */ -package org.argeo.init.a2; \ No newline at end of file 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; + } + +}