From c155192cfcd5ca355eb933fa3f55dbad6d01b958 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 17 Dec 2019 10:52:26 +0100 Subject: [PATCH] Improve A2 provisioning framework. --- .../src/org/argeo/osgi/boot/OsgiBoot.java | 14 +- .../argeo/osgi/boot/OsgiBootException.java | 2 +- .../src/org/argeo/osgi/boot/a2/A2Branch.java | 4 +- .../org/argeo/osgi/boot/a2/A2Component.java | 2 +- .../argeo/osgi/boot/a2/A2Contribution.java | 11 +- .../org/argeo/osgi/boot/a2/A2Exception.java | 15 ++ .../src/org/argeo/osgi/boot/a2/A2Source.java | 7 + .../boot/a2/AbstractProvisioningSource.java | 212 ++++++++++++++++++ .../argeo/osgi/boot/a2/ClasspathSource.java | 4 +- .../org/argeo/osgi/boot/a2/FsA2Source.java | 6 +- .../org/argeo/osgi/boot/a2/FsM2Source.java | 4 +- .../org/argeo/osgi/boot/a2/OsgiContext.java | 6 +- .../osgi/boot/a2/ProvisioningManager.java | 19 +- .../osgi/boot/a2/ProvisioningSource.java | 192 +--------------- 14 files changed, 286 insertions(+), 212 deletions(-) create mode 100644 org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Exception.java create mode 100644 org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Source.java create mode 100644 org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/AbstractProvisioningSource.java 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 4c3ee40f0..6824ecaf5 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 @@ -19,6 +19,7 @@ import static org.argeo.osgi.boot.OsgiBootUtils.debug; import static org.argeo.osgi.boot.OsgiBootUtils.warn; import java.io.File; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; @@ -31,6 +32,7 @@ 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.boot.internal.springutil.AntPathMatcher; import org.argeo.osgi.boot.internal.springutil.PathMatcher; @@ -101,7 +103,8 @@ public class OsgiBoot implements OsgiBootConstants { /** Constructor */ public OsgiBoot(BundleContext bundleContext) { this.bundleContext = bundleContext; - String homeUri = Paths.get(System.getProperty("user.home")).toUri().toString(); + Path homePath = Paths.get(System.getProperty("user.home")).toAbsolutePath(); + String homeUri = homePath.toUri().toString(); localCache = getProperty(PROP_ARGEO_OSGI_LOCAL_CACHE, homeUri + ".m2/repository/"); provisioningManager = new ProvisioningManager(bundleContext); @@ -110,7 +113,14 @@ public class OsgiBoot implements OsgiBootConstants { provisioningManager.registerDefaultSource(); } else { for (String source : sources.split(",")) { - provisioningManager.registerSource(source); + if (source.trim().equals(A2Source.DEFAULT_A2_URI)) { + provisioningManager + .registerSource(A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/osgi"); + provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/osgi"); + provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/osgi"); + } else { + provisioningManager.registerSource(source); + } } } } diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootException.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootException.java index 49c5e81b1..dd2b74d2d 100644 --- a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootException.java +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootException.java @@ -16,7 +16,7 @@ package org.argeo.osgi.boot; /** OsgiBoot specific exceptions */ -public class OsgiBootException extends RuntimeException { +class OsgiBootException extends RuntimeException { private static final long serialVersionUID = 2414011711711425353L; public OsgiBootException() { 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 index a9e5e5f90..9e3f81dec 100644 --- 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 @@ -12,13 +12,13 @@ import org.osgi.framework.Version; * typically a combination of major and minor version, indicating backward * compatibility. */ -class A2Branch implements Comparable { +public class A2Branch implements Comparable { private final A2Component component; private final String id; final SortedMap modules = Collections.synchronizedSortedMap(new TreeMap<>()); - A2Branch(A2Component component, String id) { + public A2Branch(A2Component component, String id) { this.component = component; this.id = id; component.branches.put(id, this); 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 index 7c14c295e..a2d5facf9 100644 --- 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 @@ -11,7 +11,7 @@ import org.osgi.framework.Version; * Bundle-SymbolicName. This is the equivalent of Maven's artifact * id. */ -class A2Component implements Comparable { +public class A2Component implements Comparable { private final A2Contribution contribution; private final String id; 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 index 847789186..e81ecadc8 100644 --- 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 @@ -8,7 +8,7 @@ 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. */ -class A2Contribution implements Comparable { +public class A2Contribution implements Comparable { final static String BOOT = "boot"; final static String RUNTIME = "runtime"; final static String CLASSPATH = "classpath"; @@ -18,11 +18,16 @@ class A2Contribution implements Comparable { 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); +// if (context != null) +// context.contributions.put(id, this); } A2Component getOrAddComponent(String componentId) { 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 new file mode 100644 index 000000000..129d38fbf --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Exception.java @@ -0,0 +1,15 @@ +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/A2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Source.java new file mode 100644 index 000000000..eb23845e2 --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/A2Source.java @@ -0,0 +1,7 @@ +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 new file mode 100644 index 000000000..c8497d0b0 --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/a2/AbstractProvisioningSource.java @@ -0,0 +1,212 @@ +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 index 8fd202a99..330f66a99 100644 --- 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 @@ -14,9 +14,9 @@ import org.osgi.framework.Version; * A provisioning source based on the linear classpath with which the JCM has * been started. */ -public class ClasspathSource extends ProvisioningSource { +public class ClasspathSource extends AbstractProvisioningSource { void load() throws IOException { - A2Contribution classpathContribution = new A2Contribution(this, A2Contribution.CLASSPATH); + 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); 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 index a8e79d457..bea8db3ea 100644 --- 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 @@ -11,8 +11,8 @@ import java.util.TreeSet; import org.argeo.osgi.boot.OsgiBootUtils; import org.osgi.framework.Version; -/** A file system {@link ProvisioningSource} in A2 format. */ -public class FsA2Source extends ProvisioningSource { +/** A file system {@link AbstractProvisioningSource} in A2 format. */ +public class FsA2Source extends AbstractProvisioningSource implements A2Source { private final Path base; public FsA2Source(Path base) { @@ -28,7 +28,7 @@ public class FsA2Source extends ProvisioningSource { String contributionId = contributionPath.getFileName().toString(); if (A2Contribution.BOOT.equals(contributionId))// skip boot continue contributions; - A2Contribution contribution = new A2Contribution(this, contributionId); + A2Contribution contribution = getOrAddContribution(contributionId); contributions.add(contribution); } } 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 index c2971759c..881452279 100644 --- 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 @@ -12,8 +12,8 @@ import java.nio.file.attribute.BasicFileAttributes; import org.argeo.osgi.boot.OsgiBootUtils; import org.osgi.framework.Version; -/** A file system {@link ProvisioningSource} in Maven 2 format. */ -public class FsM2Source extends ProvisioningSource { +/** A file system {@link AbstractProvisioningSource} in Maven 2 format. */ +public class FsM2Source extends AbstractProvisioningSource { private final Path base; public FsM2Source(Path base) { 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 index 948cbe26a..b4f101887 100644 --- 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 @@ -6,8 +6,8 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.Version; -/** A running OSGi bundle context seen as a {@link ProvisioningSource}. */ -class OsgiContext extends ProvisioningSource { +/** A running OSGi bundle context seen as a {@link AbstractProvisioningSource}. */ +class OsgiContext extends AbstractProvisioningSource { private final BundleContext bc; public OsgiContext(BundleContext bc) { @@ -24,7 +24,7 @@ class OsgiContext extends ProvisioningSource { } void load() { - A2Contribution runtimeContribution = new A2Contribution(this, A2Contribution.RUNTIME); + A2Contribution runtimeContribution = getOrAddContribution( A2Contribution.RUNTIME); for (Bundle bundle : bc.getBundles()) { // OsgiBootUtils.debug(bundle.getDataFile("/")); String componentId = bundle.getSymbolicName(); 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 index ef9c04fd9..c88af39d9 100644 --- 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 @@ -13,7 +13,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.argeo.osgi.boot.OsgiBootException; import org.argeo.osgi.boot.OsgiBootUtils; import org.eclipse.osgi.launch.EquinoxFactory; import org.osgi.framework.Bundle; @@ -35,13 +34,13 @@ public class ProvisioningManager { osgiContext.load(); } - void addSource(ProvisioningSource context) { - sources.add(context); + protected void addSource(ProvisioningSource source) { + sources.add(source); } - void installWholeSource(ProvisioningSource context) { + void installWholeSource(ProvisioningSource source) { Set updatedBundles = new HashSet<>(); - for (A2Contribution contribution : context.contributions.values()) { + for (A2Contribution contribution : source.listContributions(null)) { for (A2Component component : contribution.components.values()) { A2Module module = component.last().last(); Bundle bundle = installOrUpdate(module); @@ -56,7 +55,7 @@ public class ProvisioningManager { public void registerSource(String uri) { try { URI u = new URI(uri); - if ("a2".equals(u.getScheme())) { + if (A2Source.SCHEME_A2.equals(u.getScheme())) { if (u.getHost() == null || "".equals(u.getHost())) { String baseStr = u.getPath(); if (File.separatorChar == '\\') {// MS Windows @@ -69,7 +68,7 @@ public class ProvisioningManager { } } } catch (Exception e) { - throw new OsgiBootException("Cannot add source " + uri, e); + throw new A2Exception("Cannot add source " + uri, e); } } @@ -84,7 +83,7 @@ public class ProvisioningManager { String baseStr = base.toString(); if (File.separatorChar == '\\')// MS Windows baseStr = '/' + baseStr.replace(File.separatorChar, '/'); - URI baseUri = new URI("a2", null, null, 0, baseStr, null, null); + 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; @@ -104,8 +103,8 @@ public class ProvisioningManager { } } - /** @return the new/updated bundle, or null if nothign was done. */ - Bundle installOrUpdate(A2Module module) { + /** @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(); 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 index a9fab396d..62d7042d6 100644 --- 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 @@ -1,195 +1,21 @@ 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.argeo.osgi.boot.OsgiBootException; 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. */ -abstract class ProvisioningSource { - final Map contributions = Collections.synchronizedSortedMap(new TreeMap<>()); - - A2Contribution getOrAddContribution(String contributionId) { - if (contributions.containsKey(contributionId)) - return contributions.get(contributionId); - else - return new A2Contribution(this, contributionId); - } - - 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); - } - } - - void asTree() { - StringBuffer buf = new StringBuffer(); - asTree("", buf); - System.out.println(buf); - } - - 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()); - - } - - 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 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 OsgiBootException("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 OsgiBootException("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 OsgiBootException("Cannot read manifest from " + manifestPath, e); - } - } else { - Path parentPath = currentPath.getParent(); - if (parentPath == null) - throw new OsgiBootException("MANIFEST.MF file not found."); - return findManifest(currentPath.getParent()); - } - } - - public Bundle install(BundleContext bc, A2Module module) throws IOException, BundleException { - 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; - } +public interface ProvisioningSource { + /** List all contributions of this source. */ + Iterable listContributions(Object filter); - public void update(Bundle bundle, A2Module module) throws IOException, BundleException { - 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); - } + /** Install a module in the OSGi runtime. */ + Bundle install(BundleContext bc, A2Module module); - 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 OsgiBootException("Cannot install OSGi bundle from " + dir, e); - } + /** 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); - 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()); - } - } } -- 2.30.2