From: Mathieu Baudier Date: Tue, 17 Dec 2019 09:53:29 +0000 (+0100) Subject: Rename A2 framework package. X-Git-Tag: argeo-commons-2.1.84~10 X-Git-Url: http://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=79e0a2a5d751c7c077e52f9ee54469656dc96a44 Rename A2 framework package. --- diff --git a/org.argeo.osgi.boot/ext/test/org/argeo/osgi/a2/ClasspathSourceTest.java b/org.argeo.osgi.boot/ext/test/org/argeo/osgi/a2/ClasspathSourceTest.java new file mode 100644 index 000000000..d087e5a3f --- /dev/null +++ b/org.argeo.osgi.boot/ext/test/org/argeo/osgi/a2/ClasspathSourceTest.java @@ -0,0 +1,31 @@ +package org.argeo.osgi.a2; + +import java.io.IOException; + +import org.argeo.osgi.a2.ClasspathSource; +import org.argeo.osgi.a2.ProvisioningManager; +import org.argeo.osgi.boot.equinox.EquinoxUtils; +import org.junit.Test; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; +import org.osgi.framework.launch.Framework; + +public class ClasspathSourceTest { + @Test + public void testProvisioning() throws IOException { + Framework framework = EquinoxUtils.launch(null); + ProvisioningManager provisioningManager = new ProvisioningManager(framework.getBundleContext()); + ClasspathSource classpathSource = new ClasspathSource(); + classpathSource.load(); + provisioningManager.addSource(classpathSource); + provisioningManager.install(null); + for (Bundle bundle : framework.getBundleContext().getBundles()) { + System.out.println(bundle.getSymbolicName() + ":" + bundle.getVersion()); + } + try { + framework.stop(); + } catch (BundleException e) { + // silent + } + } +} diff --git a/org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/a2/ClasspathSourceTest.java b/org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/a2/ClasspathSourceTest.java deleted file mode 100644 index 6f4b46733..000000000 --- a/org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/a2/ClasspathSourceTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import java.io.IOException; - -import org.argeo.osgi.boot.equinox.EquinoxUtils; -import org.junit.Test; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleException; -import org.osgi.framework.launch.Framework; - -public class ClasspathSourceTest { - @Test - public void testProvisioning() throws IOException { - Framework framework = EquinoxUtils.launch(null); - ProvisioningManager provisioningManager = new ProvisioningManager(framework.getBundleContext()); - ClasspathSource classpathSource = new ClasspathSource(); - classpathSource.load(); - provisioningManager.addSource(classpathSource); - provisioningManager.install(null); - for (Bundle bundle : framework.getBundleContext().getBundles()) { - System.out.println(bundle.getSymbolicName() + ":" + bundle.getVersion()); - } - try { - framework.stop(); - } catch (BundleException e) { - // silent - } - } -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Branch.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Branch.java new file mode 100644 index 000000000..070830e57 --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Branch.java @@ -0,0 +1,85 @@ +package org.argeo.osgi.a2; + +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.argeo.osgi.boot.OsgiBootUtils; +import org.osgi.framework.Version; + +/** + * A logical linear sequence of versions of a given {@link A2Component}. This is + * typically a combination of major and minor version, indicating backward + * compatibility. + */ +public class A2Branch implements Comparable { + private final A2Component component; + private final String id; + + final SortedMap modules = Collections.synchronizedSortedMap(new TreeMap<>()); + + public A2Branch(A2Component component, String id) { + this.component = component; + this.id = id; + component.branches.put(id, this); + } + + 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); + } + + A2Module last() { + return modules.get(modules.lastKey()); + } + + A2Module first() { + return modules.get(modules.firstKey()); + } + + A2Component getComponent() { + return component; + } + + String getId() { + return id; + } + + @Override + public int compareTo(A2Branch o) { + return id.compareTo(id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof A2Branch) { + A2Branch o = (A2Branch) obj; + return component.equals(o.component) && id.equals(o.id); + } else + return false; + } + + @Override + public String toString() { + return getCoordinates(); + } + + public String getCoordinates() { + return component + ":" + id; + } + + static String versionToBranchId(Version version) { + return version.getMajor() + "." + version.getMinor(); + } +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Component.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Component.java new file mode 100644 index 000000000..0b5d13cce --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Component.java @@ -0,0 +1,100 @@ +package org.argeo.osgi.a2; + +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.osgi.framework.Version; + +/** + * The logical name of a software package. In OSGi's case this is + * Bundle-SymbolicName. This is the equivalent of Maven's artifact + * id. + */ +public class A2Component implements Comparable { + private final A2Contribution contribution; + private final String id; + + final SortedMap branches = Collections.synchronizedSortedMap(new TreeMap<>()); + + public A2Component(A2Contribution contribution, String id) { + this.contribution = contribution; + this.id = id; + contribution.components.put(id, this); + } + + A2Branch getOrAddBranch(String branchId) { + if (branches.containsKey(branchId)) + return branches.get(branchId); + else + return new A2Branch(this, branchId); + } + + A2Module getOrAddModule(Version version, Object locator) { + A2Branch branch = getOrAddBranch(A2Branch.versionToBranchId(version)); + A2Module module = branch.getOrAddModule(version, locator); + return module; + } + + A2Branch last() { + return branches.get(branches.lastKey()); + } + + A2Contribution getContribution() { + return contribution; + } + + String getId() { + return id; + } + + @Override + public int compareTo(A2Component o) { + return id.compareTo(o.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof A2Component) { + A2Component o = (A2Component) obj; + return contribution.equals(o.contribution) && id.equals(o.id); + } else + return false; + } + + @Override + public String toString() { + return contribution.getId() + ":" + id; + } + + void asTree(String prefix, StringBuffer buf) { + if (prefix == null) + prefix = ""; + A2Branch lastBranch = last(); + SortedMap displayMap = new TreeMap<>(Collections.reverseOrder()); + displayMap.putAll(branches); + for (String branchId : displayMap.keySet()) { + A2Branch branch = displayMap.get(branchId); + if (!lastBranch.equals(branch)) { + buf.append('\n'); + buf.append(prefix); + } else { + buf.append(" -"); + } + buf.append(prefix); + buf.append(branchId); + A2Module first = branch.first(); + A2Module last = branch.last(); + buf.append(" (").append(last.getVersion()); + if (!first.equals(last)) + buf.append(" ... ").append(first.getVersion()); + buf.append(')'); + } + } + +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Contribution.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Contribution.java new file mode 100644 index 000000000..35d5f9b03 --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Contribution.java @@ -0,0 +1,84 @@ +package org.argeo.osgi.a2; + +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +/** + * A category grouping a set of {@link A2Component}, typically based on the + * provider of these components. This is the equivalent of Maven's group Id. + */ +public class A2Contribution implements Comparable { + final static String BOOT = "boot"; + final static String RUNTIME = "runtime"; + final static String CLASSPATH = "classpath"; + + private final ProvisioningSource source; + private final String id; + + final Map components = Collections.synchronizedSortedMap(new TreeMap<>()); + + /** + * The contribution must be added to the source. Rather use + * {@link AbstractProvisioningSource#getOrAddContribution(String)} than this + * contructor directly. + */ + public A2Contribution(ProvisioningSource context, String id) { + this.source = context; + this.id = id; +// if (context != null) +// context.contributions.put(id, this); + } + + 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'); + } + } + +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Exception.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Exception.java new file mode 100644 index 000000000..83c743afe --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Exception.java @@ -0,0 +1,15 @@ +package org.argeo.osgi.a2; + +/** Unchecked A2 provisioning exception. */ +public class A2Exception extends RuntimeException { + private static final long serialVersionUID = 1927603558545397360L; + + public A2Exception(String message, Throwable e) { + super(message, e); + } + + public A2Exception(String message) { + super(message); + } + +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Module.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Module.java new file mode 100644 index 000000000..77f81d130 --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Module.java @@ -0,0 +1,62 @@ +package org.argeo.osgi.a2; + +import org.osgi.framework.Version; + +/** + * An identified software package. In OSGi's case this is the combination of + * Bundle-SymbolicName and Bundle-version. This is the + * equivalent of the full coordinates of a Maven artifact version. + */ +class A2Module implements Comparable { + private final A2Branch branch; + private final Version version; + private final Object locator; + + public A2Module(A2Branch branch, Version version, Object locator) { + this.branch = branch; + this.version = version; + this.locator = locator; + branch.modules.put(version, this); + } + + A2Branch getBranch() { + return branch; + } + + Version getVersion() { + return version; + } + + Object getLocator() { + return locator; + } + + @Override + public int compareTo(A2Module o) { + return version.compareTo(o.version); + } + + @Override + public int hashCode() { + return version.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof A2Module) { + A2Module o = (A2Module) obj; + return branch.equals(o.branch) && version.equals(o.version); + } else + return false; + } + + @Override + public String toString() { + return getCoordinates(); + } + + public String getCoordinates() { + return branch.getComponent() + ":" + version; + } + +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Source.java new file mode 100644 index 000000000..2c88a385f --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Source.java @@ -0,0 +1,7 @@ +package org.argeo.osgi.a2; + +/** A provisioning source in A2 format. */ +public interface A2Source extends ProvisioningSource { + final static String SCHEME_A2 = "a2"; + final static String DEFAULT_A2_URI = SCHEME_A2 + ":///"; +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/AbstractProvisioningSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/AbstractProvisioningSource.java new file mode 100644 index 000000000..32c175d3a --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/AbstractProvisioningSource.java @@ -0,0 +1,212 @@ +package org.argeo.osgi.a2; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; + +/** Where components are retrieved from. */ +public abstract class AbstractProvisioningSource implements ProvisioningSource { + protected final Map contributions = Collections.synchronizedSortedMap(new TreeMap<>()); + + public Iterable listContributions(Object filter) { + return contributions.values(); + } + + @Override + public Bundle install(BundleContext bc, A2Module module) { + try { + Path tempJar = null; + if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator())) + tempJar = toTempJar((Path) module.getLocator()); + Bundle bundle; + try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) { + bundle = bc.installBundle(module.getBranch().getCoordinates(), in); + } + if (tempJar != null) + Files.deleteIfExists(tempJar); + return bundle; + } catch (BundleException | IOException e) { + throw new A2Exception("Cannot install module " + module, e); + } + } + + @Override + public void update(Bundle bundle, A2Module module) { + try { + Path tempJar = null; + if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator())) + tempJar = toTempJar((Path) module.getLocator()); + try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) { + bundle.update(in); + } + if (tempJar != null) + Files.deleteIfExists(tempJar); + } catch (BundleException | IOException e) { + throw new A2Exception("Cannot update module " + module, e); + } + } + + @Override + public A2Branch findBranch(String componentId, Version version) { + A2Component component = findComponent(componentId); + if (component == null) + return null; + String branchId = version.getMajor() + "." + version.getMinor(); + if (!component.branches.containsKey(branchId)) + return null; + return component.branches.get(branchId); + } + + protected A2Contribution getOrAddContribution(String contributionId) { + if (contributions.containsKey(contributionId)) + return contributions.get(contributionId); + else { + A2Contribution contribution = new A2Contribution(this, contributionId); + contributions.put(contributionId, contribution); + return contribution; + } + } + + protected void asTree(String prefix, StringBuffer buf) { + if (prefix == null) + prefix = ""; + for (String contributionId : contributions.keySet()) { + buf.append(prefix); + buf.append(contributionId); + buf.append('\n'); + A2Contribution contribution = contributions.get(contributionId); + contribution.asTree(prefix + " ", buf); + } + } + + protected void asTree() { + StringBuffer buf = new StringBuffer(); + asTree("", buf); + System.out.println(buf); + } + + protected A2Component findComponent(String componentId) { + SortedMap res = new TreeMap<>(); + for (A2Contribution contribution : contributions.values()) { + components: for (String componentIdKey : contribution.components.keySet()) { + if (componentId.equals(componentIdKey)) { + res.put(contribution, contribution.components.get(componentIdKey)); + break components; + } + } + } + if (res.size() == 0) + return null; + // TODO explicit contribution priorities + return res.get(res.lastKey()); + + } + + protected String 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; + } + + private static Manifest findManifest(Path currentPath) { + Path metaInfPath = currentPath.resolve("META-INF"); + if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) { + Path manifestPath = metaInfPath.resolve("MANIFEST.MF"); + try { + try (InputStream in = Files.newInputStream(manifestPath)) { + Manifest manifest = new Manifest(in); + return manifest; + } + } catch (IOException e) { + throw new A2Exception("Cannot read manifest from " + manifestPath, e); + } + } else { + Path parentPath = currentPath.getParent(); + if (parentPath == null) + throw new A2Exception("MANIFEST.MF file not found."); + return findManifest(currentPath.getParent()); + } + } + + private static Path toTempJar(Path dir) { + try { + Manifest manifest = findManifest(dir); + Path jarPath = Files.createTempFile("a2Source", ".jar"); + try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) { + Files.walkFileTree(dir, new SimpleFileVisitor() { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path relPath = dir.relativize(file); + // skip MANIFEST from folder + if (relPath.toString().contentEquals("META-INF/MANIFEST.MF")) + return FileVisitResult.CONTINUE; + zos.putNextEntry(new ZipEntry(relPath.toString())); + Files.copy(file, zos); + zos.closeEntry(); + return FileVisitResult.CONTINUE; + } + }); + } + return jarPath; + } catch (IOException e) { + throw new A2Exception("Cannot install OSGi bundle from " + dir, e); + } + + } + + 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()); + } + } +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ClasspathSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ClasspathSource.java new file mode 100644 index 000000000..ea346663d --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ClasspathSource.java @@ -0,0 +1,38 @@ +package org.argeo.osgi.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.osgi.boot.OsgiBootUtils; +import org.osgi.framework.Version; + +/** + * A provisioning source based on the linear classpath with which the JCM has + * been started. + */ +public class ClasspathSource extends AbstractProvisioningSource { + void load() throws IOException { + A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH); + List classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator)); + parts: for (String part : classpath) { + Path file = Paths.get(part); + Version version; + try { + version = new Version(readVersionFromModule(file)); + } catch (Exception e) { + // ignore non OSGi + continue parts; + } + String moduleName = readSymbolicNameFromModule(file); + A2Component component = classpathContribution.getOrAddComponent(moduleName); + A2Module module = component.getOrAddModule(version, file); + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Registered " + module); + } + + } +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsA2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsA2Source.java new file mode 100644 index 000000000..eea59ded6 --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsA2Source.java @@ -0,0 +1,88 @@ +package org.argeo.osgi.a2; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.argeo.osgi.boot.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; + + public FsA2Source(Path base) { + super(); + this.base = base; + } + + void load() throws IOException { + DirectoryStream contributionPaths = Files.newDirectoryStream(base); + SortedSet contributions = new TreeSet<>(); + contributions: for (Path contributionPath : contributionPaths) { + if (Files.isDirectory(contributionPath)) { + String contributionId = contributionPath.getFileName().toString(); + if (A2Contribution.BOOT.equals(contributionId))// skip boot + continue contributions; + A2Contribution contribution = getOrAddContribution(contributionId); + contributions.add(contribution); + } + } + + for (A2Contribution contribution : contributions) { + DirectoryStream modulePaths = Files.newDirectoryStream(base.resolve(contribution.getId())); + 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; + String moduleName = moduleFileName.substring(0, lastDot); + if (moduleName.endsWith("-SNAPSHOT")) + moduleName = moduleName.substring(0, moduleName.length() - "-SNAPSHOT".length()); + int lastDash = moduleName.lastIndexOf('-'); + String versionStr = moduleName.substring(lastDash + 1); + String componentName = moduleName.substring(0, lastDash); + // if(versionStr.endsWith("-SNAPSHOT")) { + // versionStr = readVersionFromModule(modulePath); + // } + Version version; + try { + version = new Version(versionStr); + } catch (Exception e) { + versionStr = readVersionFromModule(modulePath); + if (versionStr != null) { + version = new Version(versionStr); + } else { + OsgiBootUtils.debug("Ignore " + modulePath + " (" + e.getMessage() + ")"); + continue modules; + } + } + A2Component component = contribution.getOrAddComponent(componentName); + A2Module module = component.getOrAddModule(version, modulePath); + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Registered " + module); + } + } + } + + } + + public static void main(String[] args) { + try { + FsA2Source context = new FsA2Source(Paths.get( + "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.77-SNAPSHOT/share/osgi")); + context.load(); + context.asTree(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsM2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsM2Source.java new file mode 100644 index 000000000..a4c3ed5df --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsM2Source.java @@ -0,0 +1,66 @@ +package org.argeo.osgi.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.osgi.boot.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(); + this.base = base; + } + + void load() throws IOException { + Files.walkFileTree(base, new ArtifactFileVisitor()); + } + + class ArtifactFileVisitor extends SimpleFileVisitor { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + // OsgiBootUtils.debug("Processing " + file); + if (file.toString().endsWith(".jar")) { + Version version; + try { + version = new Version(readVersionFromModule(file)); + } catch (Exception e) { + // ignore non OSGi + return FileVisitResult.CONTINUE; + } + String moduleName = readSymbolicNameFromModule(file); + Path groupPath = file.getParent().getParent().getParent(); + Path relGroupPath = base.relativize(groupPath); + String contributionName = relGroupPath.toString().replace(File.separatorChar, '.'); + A2Contribution contribution = getOrAddContribution(contributionName); + A2Component component = contribution.getOrAddComponent(moduleName); + A2Module module = component.getOrAddModule(version, file); + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Registered " + module); + } + return super.visitFile(file, attrs); + } + + } + + public static void main(String[] args) { + try { + FsM2Source context = new FsM2Source(Paths.get("/home/mbaudier/.m2/repository")); + context.load(); + context.asTree(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/OsgiContext.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/OsgiContext.java new file mode 100644 index 000000000..8630dc26d --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/OsgiContext.java @@ -0,0 +1,39 @@ +package org.argeo.osgi.a2; + +import org.argeo.osgi.boot.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; + + public OsgiContext(BundleContext bc) { + super(); + this.bc = bc; + } + + public OsgiContext() { + 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() { + A2Contribution runtimeContribution = getOrAddContribution( A2Contribution.RUNTIME); + for (Bundle bundle : bc.getBundles()) { + // OsgiBootUtils.debug(bundle.getDataFile("/")); + 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 " + module + " (location id: " + bundle.getLocation() + ")"); + } + + } +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningManager.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningManager.java new file mode 100644 index 000000000..dd7fa3b0e --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningManager.java @@ -0,0 +1,197 @@ +package org.argeo.osgi.a2; + +import java.io.File; +import java.net.URI; +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.List; +import java.util.Map; +import java.util.Set; + +import org.argeo.osgi.boot.OsgiBootUtils; +import org.eclipse.osgi.launch.EquinoxFactory; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; +import org.osgi.framework.launch.Framework; +import org.osgi.framework.wiring.FrameworkWiring; + +/** Loads provisioning sources into an OSGi context. */ +public class ProvisioningManager { + BundleContext bc; + OsgiContext osgiContext; + List sources = Collections.synchronizedList(new ArrayList<>()); + + public ProvisioningManager(BundleContext bc) { + this.bc = bc; + osgiContext = new OsgiContext(bc); + osgiContext.load(); + } + + protected void addSource(ProvisioningSource source) { + sources.add(source); + } + + void installWholeSource(ProvisioningSource source) { + Set updatedBundles = new HashSet<>(); + for (A2Contribution contribution : source.listContributions(null)) { + for (A2Component component : contribution.components.values()) { + A2Module module = component.last().last(); + Bundle bundle = installOrUpdate(module); + if (bundle != null) + updatedBundles.add(bundle); + } + } + FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); + frameworkWiring.refreshBundles(updatedBundles); + } + + public void registerSource(String uri) { + try { + URI u = new URI(uri); + if (A2Source.SCHEME_A2.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); + FsA2Source source = new FsA2Source(base); + source.load(); + addSource(source); + } + } + } 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.info("Registered " + baseUri + " as default source"); + 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 = bc.installBundle(module.getBranch().getCoordinates(), +// moduleSource.newInputStream(module.getLocator())); + Bundle bundle = moduleSource.install(bc, module); + 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 + Bundle bundle = (Bundle) lastOsgiModule.getLocator(); +// bundle.update(moduleSource.newInputStream(module.getLocator())); + moduleSource.update(bundle, module); + OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); + return bundle; + } + } + } catch (Exception e) { + OsgiBootUtils.error("Could not install module " + module + ": " + e.getMessage(), e); + } + return null; + } + + public Collection update() { + boolean fragmentsUpdated = false; + Set updatedBundles = new HashSet<>(); + bundles: for (Bundle bundle : bc.getBundles()) { + for (ProvisioningSource source : sources) { + String componentId = bundle.getSymbolicName(); + Version version = bundle.getVersion(); + A2Branch branch = source.findBranch(componentId, version); + if (branch == null) + continue bundles; + A2Module module = branch.last(); + Version moduleVersion = module.getVersion(); + int compare = moduleVersion.compareTo(version); + if (compare > 0) {// update + try { + source.update(bundle, module); +// bundle.update(in); + String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST); + if (fragmentHost != null) + fragmentsUpdated = true; + OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); + updatedBundles.add(bundle); + } catch (Exception e) { + OsgiBootUtils.error("Cannot update with module " + module, e); + } + } + } + } + FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); + if (fragmentsUpdated)// refresh all + frameworkWiring.refreshBundles(null); + else + frameworkWiring.refreshBundles(updatedBundles); + return updatedBundles; + } + + public static void main(String[] args) { + Map configuration = new HashMap<>(); + configuration.put("osgi.console", "2323"); + Framework framework = OsgiBootUtils.launch(new EquinoxFactory(), configuration); + try { + ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext()); + FsA2Source context = new FsA2Source(Paths.get( + "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.74-SNAPSHOT/argeo-node/share/osgi")); + context.load(); + if (framework.getBundleContext().getBundles().length == 1) {// initial + pm.install(null); + } else { + pm.update(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // framework.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningSource.java new file mode 100644 index 000000000..7d6fadf7b --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningSource.java @@ -0,0 +1,21 @@ +package org.argeo.osgi.a2; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Version; + +/** Where components are retrieved from. */ +public interface ProvisioningSource { + /** List all contributions of this source. */ + Iterable listContributions(Object filter); + + /** Install a module in the OSGi runtime. */ + Bundle install(BundleContext bc, A2Module module); + + /** Update a module in the OSGi runtime. */ + void update(Bundle bundle, A2Module module); + + /** Finds the {@link A2Branch} related to this component and version. */ + A2Branch findBranch(String componentId, Version version); + +} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBoot.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBoot.java index 6824ecaf5..38da0479f 100644 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBoot.java +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBoot.java @@ -32,8 +32,8 @@ import java.util.SortedMap; import java.util.StringTokenizer; import java.util.TreeMap; -import org.argeo.osgi.boot.a2.A2Source; -import org.argeo.osgi.boot.a2.ProvisioningManager; +import org.argeo.osgi.a2.A2Source; +import org.argeo.osgi.a2.ProvisioningManager; import org.argeo.osgi.boot.internal.springutil.AntPathMatcher; import org.argeo.osgi.boot.internal.springutil.PathMatcher; import org.argeo.osgi.boot.internal.springutil.SystemPropertyUtils; diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Branch.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Branch.java deleted file mode 100644 index 9e3f81dec..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Branch.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import java.util.Collections; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.argeo.osgi.boot.OsgiBootUtils; -import org.osgi.framework.Version; - -/** - * A logical linear sequence of versions of a given {@link A2Component}. This is - * typically a combination of major and minor version, indicating backward - * compatibility. - */ -public class A2Branch implements Comparable { - private final A2Component component; - private final String id; - - final SortedMap modules = Collections.synchronizedSortedMap(new TreeMap<>()); - - public A2Branch(A2Component component, String id) { - this.component = component; - this.id = id; - component.branches.put(id, this); - } - - 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); - } - - A2Module last() { - return modules.get(modules.lastKey()); - } - - A2Module first() { - return modules.get(modules.firstKey()); - } - - A2Component getComponent() { - return component; - } - - String getId() { - return id; - } - - @Override - public int compareTo(A2Branch o) { - return id.compareTo(id); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof A2Branch) { - A2Branch o = (A2Branch) obj; - return component.equals(o.component) && id.equals(o.id); - } else - return false; - } - - @Override - public String toString() { - return getCoordinates(); - } - - public String getCoordinates() { - return component + ":" + id; - } - - static String versionToBranchId(Version version) { - return version.getMajor() + "." + version.getMinor(); - } -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Component.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Component.java deleted file mode 100644 index a2d5facf9..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Component.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import java.util.Collections; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.osgi.framework.Version; - -/** - * The logical name of a software package. In OSGi's case this is - * Bundle-SymbolicName. This is the equivalent of Maven's artifact - * id. - */ -public class A2Component implements Comparable { - private final A2Contribution contribution; - private final String id; - - final SortedMap branches = Collections.synchronizedSortedMap(new TreeMap<>()); - - public A2Component(A2Contribution contribution, String id) { - this.contribution = contribution; - this.id = id; - contribution.components.put(id, this); - } - - A2Branch getOrAddBranch(String branchId) { - if (branches.containsKey(branchId)) - return branches.get(branchId); - else - return new A2Branch(this, branchId); - } - - A2Module getOrAddModule(Version version, Object locator) { - A2Branch branch = getOrAddBranch(A2Branch.versionToBranchId(version)); - A2Module module = branch.getOrAddModule(version, locator); - return module; - } - - A2Branch last() { - return branches.get(branches.lastKey()); - } - - A2Contribution getContribution() { - return contribution; - } - - String getId() { - return id; - } - - @Override - public int compareTo(A2Component o) { - return id.compareTo(o.id); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof A2Component) { - A2Component o = (A2Component) obj; - return contribution.equals(o.contribution) && id.equals(o.id); - } else - return false; - } - - @Override - public String toString() { - return contribution.getId() + ":" + id; - } - - void asTree(String prefix, StringBuffer buf) { - if (prefix == null) - prefix = ""; - A2Branch lastBranch = last(); - SortedMap displayMap = new TreeMap<>(Collections.reverseOrder()); - displayMap.putAll(branches); - for (String branchId : displayMap.keySet()) { - A2Branch branch = displayMap.get(branchId); - if (!lastBranch.equals(branch)) { - buf.append('\n'); - buf.append(prefix); - } else { - buf.append(" -"); - } - buf.append(prefix); - buf.append(branchId); - A2Module first = branch.first(); - A2Module last = branch.last(); - buf.append(" (").append(last.getVersion()); - if (!first.equals(last)) - buf.append(" ... ").append(first.getVersion()); - buf.append(')'); - } - } - -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Contribution.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Contribution.java deleted file mode 100644 index e81ecadc8..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Contribution.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; - -/** - * A category grouping a set of {@link A2Component}, typically based on the - * provider of these components. This is the equivalent of Maven's group Id. - */ -public class A2Contribution implements Comparable { - final static String BOOT = "boot"; - final static String RUNTIME = "runtime"; - final static String CLASSPATH = "classpath"; - - private final ProvisioningSource source; - private final String id; - - final Map components = Collections.synchronizedSortedMap(new TreeMap<>()); - - /** - * The contribution must be added to the source. Rather use - * {@link AbstractProvisioningSource#getOrAddContribution(String)} than this - * contructor directly. - */ - public A2Contribution(ProvisioningSource context, String id) { - this.source = context; - this.id = id; -// if (context != null) -// context.contributions.put(id, this); - } - - 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'); - } - } - -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Exception.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Exception.java deleted file mode 100644 index 129d38fbf..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Exception.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.osgi.boot.a2; - -/** Unchecked A2 provisioning exception. */ -public class A2Exception extends RuntimeException { - private static final long serialVersionUID = 1927603558545397360L; - - public A2Exception(String message, Throwable e) { - super(message, e); - } - - public A2Exception(String message) { - super(message); - } - -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Module.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Module.java deleted file mode 100644 index 44f94e334..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Module.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import org.osgi.framework.Version; - -/** - * An identified software package. In OSGi's case this is the combination of - * Bundle-SymbolicName and Bundle-version. This is the - * equivalent of the full coordinates of a Maven artifact version. - */ -class A2Module implements Comparable { - private final A2Branch branch; - private final Version version; - private final Object locator; - - public A2Module(A2Branch branch, Version version, Object locator) { - this.branch = branch; - this.version = version; - this.locator = locator; - branch.modules.put(version, this); - } - - A2Branch getBranch() { - return branch; - } - - Version getVersion() { - return version; - } - - Object getLocator() { - return locator; - } - - @Override - public int compareTo(A2Module o) { - return version.compareTo(o.version); - } - - @Override - public int hashCode() { - return version.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof A2Module) { - A2Module o = (A2Module) obj; - return branch.equals(o.branch) && version.equals(o.version); - } else - return false; - } - - @Override - public String toString() { - return getCoordinates(); - } - - public String getCoordinates() { - return branch.getComponent() + ":" + version; - } - -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Source.java deleted file mode 100644 index eb23845e2..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Source.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.osgi.boot.a2; - -/** A provisioning source in A2 format. */ -public interface A2Source extends ProvisioningSource { - final static String SCHEME_A2 = "a2"; - final static String DEFAULT_A2_URI = SCHEME_A2 + ":///"; -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/AbstractProvisioningSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/AbstractProvisioningSource.java deleted file mode 100644 index c8497d0b0..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/AbstractProvisioningSource.java +++ /dev/null @@ -1,212 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Collections; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; -import org.osgi.framework.Constants; -import org.osgi.framework.Version; - -/** Where components are retrieved from. */ -public abstract class AbstractProvisioningSource implements ProvisioningSource { - protected final Map contributions = Collections.synchronizedSortedMap(new TreeMap<>()); - - public Iterable listContributions(Object filter) { - return contributions.values(); - } - - @Override - public Bundle install(BundleContext bc, A2Module module) { - try { - Path tempJar = null; - if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator())) - tempJar = toTempJar((Path) module.getLocator()); - Bundle bundle; - try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) { - bundle = bc.installBundle(module.getBranch().getCoordinates(), in); - } - if (tempJar != null) - Files.deleteIfExists(tempJar); - return bundle; - } catch (BundleException | IOException e) { - throw new A2Exception("Cannot install module " + module, e); - } - } - - @Override - public void update(Bundle bundle, A2Module module) { - try { - Path tempJar = null; - if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator())) - tempJar = toTempJar((Path) module.getLocator()); - try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) { - bundle.update(in); - } - if (tempJar != null) - Files.deleteIfExists(tempJar); - } catch (BundleException | IOException e) { - throw new A2Exception("Cannot update module " + module, e); - } - } - - @Override - public A2Branch findBranch(String componentId, Version version) { - A2Component component = findComponent(componentId); - if (component == null) - return null; - String branchId = version.getMajor() + "." + version.getMinor(); - if (!component.branches.containsKey(branchId)) - return null; - return component.branches.get(branchId); - } - - protected A2Contribution getOrAddContribution(String contributionId) { - if (contributions.containsKey(contributionId)) - return contributions.get(contributionId); - else { - A2Contribution contribution = new A2Contribution(this, contributionId); - contributions.put(contributionId, contribution); - return contribution; - } - } - - protected void asTree(String prefix, StringBuffer buf) { - if (prefix == null) - prefix = ""; - for (String contributionId : contributions.keySet()) { - buf.append(prefix); - buf.append(contributionId); - buf.append('\n'); - A2Contribution contribution = contributions.get(contributionId); - contribution.asTree(prefix + " ", buf); - } - } - - protected void asTree() { - StringBuffer buf = new StringBuffer(); - asTree("", buf); - System.out.println(buf); - } - - protected A2Component findComponent(String componentId) { - SortedMap res = new TreeMap<>(); - for (A2Contribution contribution : contributions.values()) { - components: for (String componentIdKey : contribution.components.keySet()) { - if (componentId.equals(componentIdKey)) { - res.put(contribution, contribution.components.get(componentIdKey)); - break components; - } - } - } - if (res.size() == 0) - return null; - // TODO explicit contribution priorities - return res.get(res.lastKey()); - - } - - protected String 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; - } - - private static Manifest findManifest(Path currentPath) { - Path metaInfPath = currentPath.resolve("META-INF"); - if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) { - Path manifestPath = metaInfPath.resolve("MANIFEST.MF"); - try { - try (InputStream in = Files.newInputStream(manifestPath)) { - Manifest manifest = new Manifest(in); - return manifest; - } - } catch (IOException e) { - throw new A2Exception("Cannot read manifest from " + manifestPath, e); - } - } else { - Path parentPath = currentPath.getParent(); - if (parentPath == null) - throw new A2Exception("MANIFEST.MF file not found."); - return findManifest(currentPath.getParent()); - } - } - - private static Path toTempJar(Path dir) { - try { - Manifest manifest = findManifest(dir); - Path jarPath = Files.createTempFile("a2Source", ".jar"); - try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) { - Files.walkFileTree(dir, new SimpleFileVisitor() { - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Path relPath = dir.relativize(file); - // skip MANIFEST from folder - if (relPath.toString().contentEquals("META-INF/MANIFEST.MF")) - return FileVisitResult.CONTINUE; - zos.putNextEntry(new ZipEntry(relPath.toString())); - Files.copy(file, zos); - zos.closeEntry(); - return FileVisitResult.CONTINUE; - } - }); - } - return jarPath; - } catch (IOException e) { - throw new A2Exception("Cannot install OSGi bundle from " + dir, e); - } - - } - - 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()); - } - } -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ClasspathSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ClasspathSource.java deleted file mode 100644 index 330f66a99..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ClasspathSource.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.argeo.osgi.boot.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.osgi.boot.OsgiBootUtils; -import org.osgi.framework.Version; - -/** - * A provisioning source based on the linear classpath with which the JCM has - * been started. - */ -public class ClasspathSource extends AbstractProvisioningSource { - void load() throws IOException { - A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH); - List classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator)); - parts: for (String part : classpath) { - Path file = Paths.get(part); - Version version; - try { - version = new Version(readVersionFromModule(file)); - } catch (Exception e) { - // ignore non OSGi - continue parts; - } - String moduleName = readSymbolicNameFromModule(file); - A2Component component = classpathContribution.getOrAddComponent(moduleName); - A2Module module = component.getOrAddModule(version, file); - if (OsgiBootUtils.isDebug()) - OsgiBootUtils.debug("Registered " + module); - } - - } -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/FsA2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/FsA2Source.java deleted file mode 100644 index bea8db3ea..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/FsA2Source.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.argeo.osgi.boot.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; - - public FsA2Source(Path base) { - super(); - this.base = base; - } - - void load() throws IOException { - DirectoryStream contributionPaths = Files.newDirectoryStream(base); - SortedSet contributions = new TreeSet<>(); - contributions: for (Path contributionPath : contributionPaths) { - if (Files.isDirectory(contributionPath)) { - String contributionId = contributionPath.getFileName().toString(); - if (A2Contribution.BOOT.equals(contributionId))// skip boot - continue contributions; - A2Contribution contribution = getOrAddContribution(contributionId); - contributions.add(contribution); - } - } - - for (A2Contribution contribution : contributions) { - DirectoryStream modulePaths = Files.newDirectoryStream(base.resolve(contribution.getId())); - 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; - String moduleName = moduleFileName.substring(0, lastDot); - if (moduleName.endsWith("-SNAPSHOT")) - moduleName = moduleName.substring(0, moduleName.length() - "-SNAPSHOT".length()); - int lastDash = moduleName.lastIndexOf('-'); - String versionStr = moduleName.substring(lastDash + 1); - String componentName = moduleName.substring(0, lastDash); - // if(versionStr.endsWith("-SNAPSHOT")) { - // versionStr = readVersionFromModule(modulePath); - // } - Version version; - try { - version = new Version(versionStr); - } catch (Exception e) { - versionStr = readVersionFromModule(modulePath); - if (versionStr != null) { - version = new Version(versionStr); - } else { - OsgiBootUtils.debug("Ignore " + modulePath + " (" + e.getMessage() + ")"); - continue modules; - } - } - A2Component component = contribution.getOrAddComponent(componentName); - A2Module module = component.getOrAddModule(version, modulePath); - if (OsgiBootUtils.isDebug()) - OsgiBootUtils.debug("Registered " + module); - } - } - } - - } - - public static void main(String[] args) { - try { - FsA2Source context = new FsA2Source(Paths.get( - "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.77-SNAPSHOT/share/osgi")); - context.load(); - context.asTree(); - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/FsM2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/FsM2Source.java deleted file mode 100644 index 881452279..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/FsM2Source.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.argeo.osgi.boot.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.osgi.boot.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(); - this.base = base; - } - - void load() throws IOException { - Files.walkFileTree(base, new ArtifactFileVisitor()); - } - - class ArtifactFileVisitor extends SimpleFileVisitor { - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - // OsgiBootUtils.debug("Processing " + file); - if (file.toString().endsWith(".jar")) { - Version version; - try { - version = new Version(readVersionFromModule(file)); - } catch (Exception e) { - // ignore non OSGi - return FileVisitResult.CONTINUE; - } - String moduleName = readSymbolicNameFromModule(file); - Path groupPath = file.getParent().getParent().getParent(); - Path relGroupPath = base.relativize(groupPath); - String contributionName = relGroupPath.toString().replace(File.separatorChar, '.'); - A2Contribution contribution = getOrAddContribution(contributionName); - A2Component component = contribution.getOrAddComponent(moduleName); - A2Module module = component.getOrAddModule(version, file); - if (OsgiBootUtils.isDebug()) - OsgiBootUtils.debug("Registered " + module); - } - return super.visitFile(file, attrs); - } - - } - - public static void main(String[] args) { - try { - FsM2Source context = new FsM2Source(Paths.get("/home/mbaudier/.m2/repository")); - context.load(); - context.asTree(); - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/OsgiContext.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/OsgiContext.java deleted file mode 100644 index b4f101887..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/OsgiContext.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import org.argeo.osgi.boot.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; - - public OsgiContext(BundleContext bc) { - super(); - this.bc = bc; - } - - public OsgiContext() { - 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() { - A2Contribution runtimeContribution = getOrAddContribution( A2Contribution.RUNTIME); - for (Bundle bundle : bc.getBundles()) { - // OsgiBootUtils.debug(bundle.getDataFile("/")); - 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 " + module + " (location id: " + bundle.getLocation() + ")"); - } - - } -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ProvisioningManager.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ProvisioningManager.java deleted file mode 100644 index c88af39d9..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ProvisioningManager.java +++ /dev/null @@ -1,197 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import java.io.File; -import java.net.URI; -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.List; -import java.util.Map; -import java.util.Set; - -import org.argeo.osgi.boot.OsgiBootUtils; -import org.eclipse.osgi.launch.EquinoxFactory; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.Version; -import org.osgi.framework.launch.Framework; -import org.osgi.framework.wiring.FrameworkWiring; - -/** Loads provisioning sources into an OSGi context. */ -public class ProvisioningManager { - BundleContext bc; - OsgiContext osgiContext; - List sources = Collections.synchronizedList(new ArrayList<>()); - - public ProvisioningManager(BundleContext bc) { - this.bc = bc; - osgiContext = new OsgiContext(bc); - osgiContext.load(); - } - - protected void addSource(ProvisioningSource source) { - sources.add(source); - } - - void installWholeSource(ProvisioningSource source) { - Set updatedBundles = new HashSet<>(); - for (A2Contribution contribution : source.listContributions(null)) { - for (A2Component component : contribution.components.values()) { - A2Module module = component.last().last(); - Bundle bundle = installOrUpdate(module); - if (bundle != null) - updatedBundles.add(bundle); - } - } - FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); - frameworkWiring.refreshBundles(updatedBundles); - } - - public void registerSource(String uri) { - try { - URI u = new URI(uri); - if (A2Source.SCHEME_A2.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); - FsA2Source source = new FsA2Source(base); - source.load(); - addSource(source); - } - } - } 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.info("Registered " + baseUri + " as default source"); - 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 = bc.installBundle(module.getBranch().getCoordinates(), -// moduleSource.newInputStream(module.getLocator())); - Bundle bundle = moduleSource.install(bc, module); - 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 - Bundle bundle = (Bundle) lastOsgiModule.getLocator(); -// bundle.update(moduleSource.newInputStream(module.getLocator())); - moduleSource.update(bundle, module); - OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); - return bundle; - } - } - } catch (Exception e) { - OsgiBootUtils.error("Could not install module " + module + ": " + e.getMessage(), e); - } - return null; - } - - public Collection update() { - boolean fragmentsUpdated = false; - Set updatedBundles = new HashSet<>(); - bundles: for (Bundle bundle : bc.getBundles()) { - for (ProvisioningSource source : sources) { - String componentId = bundle.getSymbolicName(); - Version version = bundle.getVersion(); - A2Branch branch = source.findBranch(componentId, version); - if (branch == null) - continue bundles; - A2Module module = branch.last(); - Version moduleVersion = module.getVersion(); - int compare = moduleVersion.compareTo(version); - if (compare > 0) {// update - try { - source.update(bundle, module); -// bundle.update(in); - String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST); - if (fragmentHost != null) - fragmentsUpdated = true; - OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion); - updatedBundles.add(bundle); - } catch (Exception e) { - OsgiBootUtils.error("Cannot update with module " + module, e); - } - } - } - } - FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); - if (fragmentsUpdated)// refresh all - frameworkWiring.refreshBundles(null); - else - frameworkWiring.refreshBundles(updatedBundles); - return updatedBundles; - } - - public static void main(String[] args) { - Map configuration = new HashMap<>(); - configuration.put("osgi.console", "2323"); - Framework framework = OsgiBootUtils.launch(new EquinoxFactory(), configuration); - try { - ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext()); - FsA2Source context = new FsA2Source(Paths.get( - "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.74-SNAPSHOT/argeo-node/share/osgi")); - context.load(); - if (framework.getBundleContext().getBundles().length == 1) {// initial - pm.install(null); - } else { - pm.update(); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - // framework.stop(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - -} diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ProvisioningSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ProvisioningSource.java deleted file mode 100644 index 62d7042d6..000000000 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/ProvisioningSource.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.osgi.boot.a2; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Version; - -/** Where components are retrieved from. */ -public interface ProvisioningSource { - /** List all contributions of this source. */ - Iterable listContributions(Object filter); - - /** Install a module in the OSGi runtime. */ - Bundle install(BundleContext bc, A2Module module); - - /** Update a module in the OSGi runtime. */ - void update(Bundle bundle, A2Module module); - - /** Finds the {@link A2Branch} related to this component and version. */ - A2Branch findBranch(String componentId, Version version); - -}