} 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;
}
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)
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 {
Import-Package: \
org.osgi.*;version=0.0.0,\
java.util.logging;resolution:=optional
+
+Export-Package: org.argeo.api.*
\ No newline at end of file
--- /dev/null
+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<A2Branch> {
+ private final A2Component component;
+ private final String id;
+
+ final SortedMap<Version, A2Module> modules = Collections.synchronizedSortedMap(new TreeMap<>());
+
+ public A2Branch(A2Component component, String id) {
+ this.component = component;
+ this.id = id;
+ component.branches.put(id, this);
+ }
+
+ public Iterable<A2Module> 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();
+ }
+}
--- /dev/null
+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
+ * <code>Bundle-SymbolicName</code>. This is the equivalent of Maven's artifact
+ * id.
+ */
+public class A2Component implements Comparable<A2Component> {
+ private final A2Contribution contribution;
+ private final String id;
+
+ final SortedMap<String, A2Branch> branches = Collections.synchronizedSortedMap(new TreeMap<>());
+
+ public A2Component(A2Contribution contribution, String id) {
+ this.contribution = contribution;
+ this.id = id;
+ contribution.components.put(id, this);
+ }
+
+ public Iterable<A2Branch> 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<String, A2Branch> 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(')');
+ }
+ }
+
+}
--- /dev/null
+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<A2Contribution> {
+ 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<String, A2Component> 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<A2Component> 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;
+ };
+ }
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+package org.argeo.api.a2;
+
+import org.osgi.framework.Version;
+
+/**
+ * An identified software package. In OSGi's case this is the combination of
+ * <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
+ * equivalent of the full coordinates of a Maven artifact version.
+ */
+public class A2Module implements Comparable<A2Module> {
+ 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;
+ }
+
+}
--- /dev/null
+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();
+}
--- /dev/null
+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<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
+
+ private final boolean usingReference;
+
+ public AbstractProvisioningSource(boolean usingReference) {
+ this.usingReference = usingReference;
+ }
+
+ public Iterable<A2Contribution> 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<A2Contribution, A2Component> 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<Path>() {
+ 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);
+ }
+
+ }
+
+}
--- /dev/null
+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<String> 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);
+ }
+
+ }
+}
--- /dev/null
+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<String, String> variantsXOr;
+
+ private final List<String> includes;
+ private final List<String> excludes;
+
+ public FsA2Source(Path base, Map<String, String> variantsXOr, boolean usingReference, List<String> includes,
+ List<String> excludes) {
+ super(usingReference);
+ this.base = base;
+ this.variantsXOr = new HashMap<>(variantsXOr);
+ this.includes = includes;
+ this.excludes = excludes;
+ }
+
+ void load() throws IOException {
+ SortedMap<Path, A2Contribution> contributions = new TreeMap<>();
+
+ DirectoryStream<Path> 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<Path> 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<String, String> getVariantsXOr() {
+ return variantsXOr;
+ }
+
+// public static void main(String[] args) {
+// if (args.length == 0)
+// throw new IllegalArgumentException("Usage: <path to A2 base>");
+// try {
+// Map<String, String> 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();
+// }
+// }
+
+}
--- /dev/null
+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<Path> {
+
+ @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();
+ }
+ }
+
+}
--- /dev/null
+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() + ")");
+
+ }
+}
--- /dev/null
+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<ProvisioningSource> 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<Bundle> 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<String, List<String>> properties = queryToMap(u);
+ Map<String, String> xOr = new HashMap<>();
+ List<String> includes = null;
+ List<String> excludes = null;
+ for (String key : properties.keySet()) {
+ List<String> 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<Bundle> update() {
+ boolean fragmentsUpdated = false;
+ Set<Bundle> 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<String, List<String>> queryToMap(URI uri) {
+ return queryToMap(uri.getQuery());
+ }
+
+ private static Map<String, List<String>> queryToMap(String queryPart) {
+ try {
+ final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
+ 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<String>());
+ }
+ 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: <path to A2 base>");
+// Map<String, String> 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<String, String> 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();
+// }
+// }
+// }
+
+}
--- /dev/null
+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<A2Contribution> 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);
+
+}
--- /dev/null
+/** A2 OSGi repository format. */
+package org.argeo.api.a2;
\ No newline at end of file
--- /dev/null
+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";
+
+}
--- /dev/null
+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;
+}
--- /dev/null
+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<Map<String, String>> configCallback);
+
+ public void stopRuntime(String relPath, boolean async);
+}
+++ /dev/null
-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;
-}
+++ /dev/null
-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<String, String> configuration = new HashMap<>();
-
- private Map<String, OsgiRuntimeContext> 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<String, String> 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<Map<String, String>> configCallback) {
- stopRuntime(relPath);
- Path stateArea = baseStateArea.resolve(relPath);
- Path configArea = baseConfigArea.resolve(relPath);
- Map<String, String> 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<Map<String, String>> 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<String, String> 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();
- }
-
-}
--- /dev/null
+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<String, String> configuration = new HashMap<>();
+
+ private Map<String, OsgiRuntimeContext> 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<String, RuntimeContext> 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<String, String> 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<Map<String, String>> configCallback) {
+ stopRuntime(relPath, false);
+ Path stateArea = baseStateArea.resolve(relPath);
+ Path configArea = baseConfigArea.resolve(relPath);
+ Map<String, String> 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<Map<String, String>> 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<String, String> 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();
+ }
+
+}
+++ /dev/null
-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<Runnable> 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<String, String> 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<String, String>) 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);
- }
-}
--- /dev/null
+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<Runnable> 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<String, String> 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<String, String>) 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);
+ }
+}
import java.util.Map;
+import org.argeo.api.init.RuntimeContext;
+
public class StaticRuntimeContext implements RuntimeContext {
private Map<String, String> config;
--- /dev/null
+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<String> 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<NetworkInterface> 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;
+ }
+ }
+ }
+
+ }
+}
+++ /dev/null
-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<A2Branch> {
- private final A2Component component;
- private final String id;
-
- final SortedMap<Version, A2Module> modules = Collections.synchronizedSortedMap(new TreeMap<>());
-
- public A2Branch(A2Component component, String id) {
- this.component = component;
- this.id = id;
- component.branches.put(id, this);
- }
-
- public Iterable<A2Module> 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();
- }
-}
+++ /dev/null
-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
- * <code>Bundle-SymbolicName</code>. This is the equivalent of Maven's artifact
- * id.
- */
-public class A2Component implements Comparable<A2Component> {
- private final A2Contribution contribution;
- private final String id;
-
- final SortedMap<String, A2Branch> branches = Collections.synchronizedSortedMap(new TreeMap<>());
-
- public A2Component(A2Contribution contribution, String id) {
- this.contribution = contribution;
- this.id = id;
- contribution.components.put(id, this);
- }
-
- public Iterable<A2Branch> 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<String, A2Branch> 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(')');
- }
- }
-
-}
+++ /dev/null
-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<A2Contribution> {
- 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<String, A2Component> 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<A2Component> 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;
- };
- }
- }
-
-}
+++ /dev/null
-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);
- }
-
-}
+++ /dev/null
-package org.argeo.init.a2;
-
-import org.osgi.framework.Version;
-
-/**
- * An identified software package. In OSGi's case this is the combination of
- * <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
- * equivalent of the full coordinates of a Maven artifact version.
- */
-public class A2Module implements Comparable<A2Module> {
- 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;
- }
-
-}
+++ /dev/null
-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();
-}
+++ /dev/null
-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<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
-
- private final boolean usingReference;
-
- public AbstractProvisioningSource(boolean usingReference) {
- this.usingReference = usingReference;
- }
-
- public Iterable<A2Contribution> 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<A2Contribution, A2Component> 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<Path>() {
- 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);
- }
-
- }
-
-}
+++ /dev/null
-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<String> 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);
- }
-
- }
-}
+++ /dev/null
-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<String, String> variantsXOr;
-
- private final List<String> includes;
- private final List<String> excludes;
-
- public FsA2Source(Path base, Map<String, String> variantsXOr, boolean usingReference, List<String> includes,
- List<String> excludes) {
- super(usingReference);
- this.base = base;
- this.variantsXOr = new HashMap<>(variantsXOr);
- this.includes = includes;
- this.excludes = excludes;
- }
-
- void load() throws IOException {
- SortedMap<Path, A2Contribution> contributions = new TreeMap<>();
-
- DirectoryStream<Path> 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<Path> 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<String, String> getVariantsXOr() {
- return variantsXOr;
- }
-
-// public static void main(String[] args) {
-// if (args.length == 0)
-// throw new IllegalArgumentException("Usage: <path to A2 base>");
-// try {
-// Map<String, String> 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();
-// }
-// }
-
-}
+++ /dev/null
-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<Path> {
-
- @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();
- }
- }
-
-}
+++ /dev/null
-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() + ")");
-
- }
-}
+++ /dev/null
-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<ProvisioningSource> 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<Bundle> 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<String, List<String>> properties = queryToMap(u);
- Map<String, String> xOr = new HashMap<>();
- List<String> includes = null;
- List<String> excludes = null;
- for (String key : properties.keySet()) {
- List<String> 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<Bundle> update() {
- boolean fragmentsUpdated = false;
- Set<Bundle> 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<String, List<String>> queryToMap(URI uri) {
- return queryToMap(uri.getQuery());
- }
-
- private static Map<String, List<String>> queryToMap(String queryPart) {
- try {
- final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
- 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<String>());
- }
- 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: <path to A2 base>");
-// Map<String, String> 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<String, String> 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();
-// }
-// }
-// }
-
-}
+++ /dev/null
-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<A2Contribution> 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);
-
-}
+++ /dev/null
-/** A2 OSGi repository format. */
-package org.argeo.init.a2;
\ No newline at end of file
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
}
private void close() {
- RuntimeContext runtimeContext = Service.getRuntimeContext();
+ RuntimeContext runtimeContext = InternalState.getMainRuntimeContext();
if (runtimeContext != null) {
try {
runtimeContext.waitForStop(0);
}
}
- 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
+// }
+//
+// }
}
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;
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);
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;
* 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";
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;
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 {
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 + "" : "") //
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;
} 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...
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);
}
// 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()));
}
}
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());
}
}
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<String, String> 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, "<not set>") + ", using " + defaultStartLevel);
- OsgiBootUtils.debug("OSGi active start level: " + getProperty(PROP_OSGI_STARTLEVEL, "<not set>")
+ + getProperty(InitConstants.PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "<not set>") + ", using " + defaultStartLevel);
+ OsgiBootUtils.debug("OSGi active start level: " + getProperty(InitConstants.PROP_OSGI_STARTLEVEL, "<not set>")
+ ", using " + activeStartLevel);
OsgiBootUtils.debug("Framework start level: " + frameworkStartLevel.getStartLevel() + " (initial: "
+ initialStartLevel + ")");
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<String> 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)) {
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;
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));
}
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())
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);
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;
--- /dev/null
+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;
+ }
+
+}