source.. = src/,\
ext/test/
-additional.bundles = org.junit
+additional.bundles = org.junit,\
+ org.hamcrest
--- /dev/null
+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
+ }
+ }
+}
import java.util.ArrayList;\r
import java.util.Date;\r
import java.util.List;\r
+import java.util.Map;\r
import java.util.StringTokenizer;\r
\r
import org.osgi.framework.Bundle;\r
+import org.osgi.framework.BundleException;\r
+import org.osgi.framework.launch.Framework;\r
+import org.osgi.framework.launch.FrameworkFactory;\r
\r
/** Utilities, mostly related to logging. */\r
public class OsgiBootUtils {\r
return comp;\r
}\r
\r
+ /** Launch an OSGi framework. */\r
+ public static Framework launch(FrameworkFactory frameworkFactory, Map<String, String> configuration) {\r
+ // start OSGi\r
+ Framework framework = frameworkFactory.newFramework(configuration);\r
+ try {\r
+ framework.start();\r
+ } catch (BundleException e) {\r
+ throw new OsgiBootException("Cannot start OSGi framework", e);\r
+ }\r
+ return framework;\r
+ }\r
+\r
}\r
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.
+ */
class A2Branch implements Comparable<A2Branch> {
private final A2Component component;
private final String id;
import org.osgi.framework.Version;
+/**
+ * The logical name of a software package. In OSGi's case this is
+ * <code>Bundle-SymbolicName</code>. This is the equivalent of Maven's artifact
+ * id.
+ */
class A2Component implements Comparable<A2Component> {
private final A2Contribution contribution;
private final String id;
if (prefix == null)
prefix = "";
A2Branch lastBranch = last();
- SortedMap<String, A2Branch> displayMap = new TreeMap<>(Collections.reverseOrder());
+ SortedMap<String, A2Branch> displayMap = new TreeMap<>(Collections.reverseOrder());
displayMap.putAll(branches);
for (String branchId : displayMap.keySet()) {
A2Branch branch = displayMap.get(branchId);
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.
+ */
class A2Contribution implements Comparable<A2Contribution> {
final static String BOOT = "boot";
final static String RUNTIME = "runtime";
+ final static String CLASSPATH = "classpath";
private final ProvisioningSource source;
private final String id;
import org.osgi.framework.Version;
+/**
+ * An identified software package. In OSGi's case this is the combination of
+ * <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
+ * equivalent of the full coordinates of a Maven artifact version.
+ */
class A2Module implements Comparable<A2Module> {
private final A2Branch branch;
private final Version version;
return branch.getComponent() + ":" + version;
}
-
}
--- /dev/null
+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 ProvisioningSource {
+ void load() throws IOException {
+ A2Contribution classpathContribution = new A2Contribution(this, A2Contribution.CLASSPATH);
+ List<String> classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator));
+ parts: for (String part : classpath) {
+ Path file = Paths.get(part);
+ Version version;
+ try {
+ version = new Version(readVersionFromModule(file));
+ } catch (Exception e) {
+ // ignore non OSGi
+ continue parts;
+ }
+ String moduleName = readSymbolicNameFromModule(file);
+ A2Component component = classpathContribution.getOrAddComponent(moduleName);
+ A2Module module = component.getOrAddModule(version, file);
+ if (OsgiBootUtils.isDebug())
+ OsgiBootUtils.debug("Registered " + module);
+ }
+
+ }
+}
import org.argeo.osgi.boot.OsgiBootUtils;
import org.osgi.framework.Version;
+/** A file system {@link ProvisioningSource} in A2 format. */
public class FsA2Source extends ProvisioningSource {
private final Path base;
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 {
private final Path base;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.Version;
+/** A running OSGi bundle context seen as a {@link ProvisioningSource}. */
class OsgiContext extends ProvisioningSource {
private final BundleContext bc;
package org.argeo.osgi.boot.a2;
import java.io.File;
-import java.io.InputStream;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.eclipse.osgi.launch.EquinoxFactory;
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;
import org.osgi.framework.launch.Framework;
-import org.osgi.framework.launch.FrameworkFactory;
import org.osgi.framework.wiring.FrameworkWiring;
+/** Loads provisioning sources into an OSGi context. */
public class ProvisioningManager {
BundleContext bc;
OsgiContext osgiContext;
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 = 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;
int compare = moduleVersion.compareTo(lastOsgiModule.getVersion());
if (compare > 0) {// update
Bundle bundle = (Bundle) lastOsgiModule.getLocator();
- bundle.update(moduleSource.newInputStream(module.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(), null);
+ OsgiBootUtils.error("Could not install module " + module + ": " + e.getMessage(), e);
}
return null;
}
Version moduleVersion = module.getVersion();
int compare = moduleVersion.compareTo(version);
if (compare > 0) {// update
- try (InputStream in = source.newInputStream(module.getLocator())) {
- bundle.update(in);
+ try {
+ source.update(bundle, module);
+// bundle.update(in);
String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST);
if (fragmentHost != null)
fragmentsUpdated = true;
return updatedBundles;
}
- private static Framework launch() {
- // start OSGi
- FrameworkFactory frameworkFactory = new EquinoxFactory();
+ public static void main(String[] args) {
Map<String, String> configuration = new HashMap<>();
configuration.put("osgi.console", "2323");
- Framework framework = frameworkFactory.newFramework(configuration);
- try {
- framework.start();
- } catch (BundleException e) {
- throw new OsgiBootException("Cannot start OSGi framework", e);
- }
- return framework;
- }
-
- public static void main(String[] args) {
- Framework framework = launch();
+ Framework framework = OsgiBootUtils.launch(new EquinoxFactory(), configuration);
try {
ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
FsA2Source context = new FsA2Source(Paths.get(
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<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
}
protected String readVersionFromModule(Path modulePath) {
- try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
- Manifest manifest = in.getManifest();
- String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
- return versionStr;
- } catch (IOException e) {
- throw new OsgiBootException("Cannot read manifest from " + modulePath, e);
+ 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) {
- try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
- Manifest manifest = in.getManifest();
- String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
- int semiColIndex = symbolicName.indexOf(';');
- if (semiColIndex >= 0)
- symbolicName = symbolicName.substring(0, semiColIndex);
- return symbolicName;
+ 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 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);
+ }
+
+ static Path toTempJar(Path dir) {
+ try {
+ Manifest manifest = findManifest(dir);
+ Path jarPath = Files.createTempFile("a2Source", ".jar");
+ try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) {
+ Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Path relPath = dir.relativize(file);
+ // skip MANIFEST from folder
+ if (relPath.toString().contentEquals("META-INF/MANIFEST.MF"))
+ return FileVisitResult.CONTINUE;
+ zos.putNextEntry(new ZipEntry(relPath.toString()));
+ Files.copy(file, zos);
+ zos.closeEntry();
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+ return jarPath;
} catch (IOException e) {
- throw new OsgiBootException("Cannot read manifest from " + modulePath, e);
+ throw new OsgiBootException("Cannot install OSGi bundle from " + dir, e);
}
+
}
- InputStream newInputStream(Object locator) throws IOException {
+ private InputStream newInputStream(Object locator) throws IOException {
if (locator instanceof Path) {
return Files.newInputStream((Path) locator);
} else if (locator instanceof URL) {
--- /dev/null
+package org.argeo.osgi.boot.equinox;
+
+import java.util.Map;
+
+import org.argeo.osgi.boot.OsgiBootUtils;
+import org.eclipse.osgi.launch.EquinoxFactory;
+import org.osgi.framework.launch.Framework;
+
+/**
+ * Utilities with a dependency to the Equinox OSGi runtime or its configuration.
+ */
+public class EquinoxUtils {
+
+ public static Framework launch(Map<String, String> configuration) {
+ return OsgiBootUtils.launch(new EquinoxFactory(), configuration);
+ }
+
+ /** Singleton. */
+ private EquinoxUtils() {
+
+ }
+}