You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by tj...@apache.org on 2020/03/04 17:14:49 UTC

[felix-atomos] branch master updated (2b935d0 -> 7576cdb)

This is an automated email from the ASF dual-hosted git repository.

tjwatson pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/felix-atomos.git.


    from 2b935d0  Merge pull request #4 from sebratton/expose-getConnectContent
     new 8925fd8  FELIX-6227 - Generalize Atomos index to be used independent of substrate
     new 7576cdb  Javadoc tweaks for getConnectContent

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |   1 -
 README.md                                          |   6 +-
 .../atomos.examples.substrate.equinox/pom.xml      |   2 +-
 .../atomos.examples.substrate.felix/pom.xml        |   2 +-
 .../impl/runtime/base/AtomosRuntimeBase.java       | 430 ++++++++++++++++++---
 .../impl/runtime/base/AtomosRuntimeClassPath.java  |  10 +-
 .../content/ConnectContentCloseableJar.java        |  89 +++++
 .../ConnectContentFile.java}                       |   6 +-
 .../ConnectContentIndexed.java}                    |  14 +-
 .../ConnectContentJar.java}                        |  27 +-
 .../{substrate => content}/package-info.java       |   2 +-
 .../impl/runtime/modules/AtomosRuntimeModules.java |  15 +-
 ...nnectContent.java => ConnectContentModule.java} |   4 +-
 .../runtime/substrate/AtomosRuntimeSubstrate.java  | 430 ---------------------
 .../substrate/SubstrateJarConnectContent.java      | 139 -------
 .../apache/felix/atomos/launch/AtomosLauncher.java |   2 +-
 .../apache/felix/atomos/runtime/AtomosContent.java |  13 +-
 .../apache/felix/atomos/runtime/AtomosRuntime.java |  23 +-
 atomos.tests/atomos.tests.index.bundles/pom.xml    |  28 ++
 .../atomos/tests/index/bundles/IndexLaunch.java}   |  11 +-
 .../tests/index/bundles/b1/ActivatorBundle1.java}  |  21 +-
 .../tests/index/bundles/b2/ActivatorBundle2.java}  |  21 +-
 .../tests/index/bundles/b3/ActivatorBundle3.java}  |  21 +-
 .../tests/index/bundles/b4/ActivatorBundle4.java}  |  21 +-
 .../main/resources/atomos/1/META-INF/MANIFEST.MF   |   5 +
 .../resources/atomos/1/OSGI-INF/bundle.1-1.txt     |   1 +
 .../resources/atomos/1/OSGI-INF/bundle.1-2.txt     |   1 +
 .../main/resources/atomos/1/OSGI-INF/common.txt    |   1 +
 .../main/resources/atomos/2/META-INF/MANIFEST.MF   |   5 +
 .../resources/atomos/2/OSGI-INF/bundle.2-1.txt     |   1 +
 .../resources/atomos/2/OSGI-INF/bundle.2-2.txt     |   1 +
 .../main/resources/atomos/2/OSGI-INF/common.txt    |   1 +
 .../main/resources/atomos/3/META-INF/MANIFEST.MF   |   5 +
 .../resources/atomos/3/OSGI-INF/bundle.3-1.txt     |   1 +
 .../resources/atomos/3/OSGI-INF/bundle.3-2.txt     |   1 +
 .../main/resources/atomos/3/OSGI-INF/common.txt    |   1 +
 .../main/resources/atomos/4/META-INF/MANIFEST.MF   |   5 +
 .../resources/atomos/4/OSGI-INF/bundle.4-1.txt     |   1 +
 .../resources/atomos/4/OSGI-INF/bundle.4-2.txt     |   1 +
 .../main/resources/atomos/4/OSGI-INF/common.txt    |   1 +
 .../src/main/resources/atomos/bundles.index        |  36 ++
 .../tests/index/bundles/IndexLaunchTest.java       | 183 +++++++++
 atomos.tests/pom.xml                               |   1 +
 43 files changed, 867 insertions(+), 723 deletions(-)
 create mode 100644 atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentCloseableJar.java
 rename atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/{base/FileConnectContent.java => content/ConnectContentFile.java} (96%)
 rename atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/{substrate/SubstrateIndexConnectContent.java => content/ConnectContentIndexed.java} (88%)
 rename atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/{base/JarConnectContent.java => content/ConnectContentJar.java} (76%)
 rename atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/{substrate => content}/package-info.java (91%)
 rename atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/{ModuleConnectContent.java => ConnectContentModule.java} (97%)
 delete mode 100644 atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/AtomosRuntimeSubstrate.java
 delete mode 100644 atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/SubstrateJarConnectContent.java
 create mode 100644 atomos.tests/atomos.tests.index.bundles/pom.xml
 copy atomos.tests/{atomos.tests.modulepath.service/src/main/java/org/apache/felix/atomos/tests/modulepath/service/ModulepathLaunch.java => atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunch.java} (73%)
 copy atomos.tests/{atomos.tests.testbundles/atomos.tests.testbundles.service.impl.activator/src/main/java/org/apache/felix/atomos/tests/testbundles/service/impl/activator/Activator.java => atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b1/ActivatorBundle1.java} (52%)
 copy atomos.tests/{atomos.tests.testbundles/atomos.tests.testbundles.service.impl.activator/src/main/java/org/apache/felix/atomos/tests/testbundles/service/impl/activator/Activator.java => atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b2/ActivatorBundle2.java} (52%)
 copy atomos.tests/{atomos.tests.testbundles/atomos.tests.testbundles.service.impl.activator/src/main/java/org/apache/felix/atomos/tests/testbundles/service/impl/activator/Activator.java => atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b3/ActivatorBundle3.java} (52%)
 copy atomos.tests/{atomos.tests.testbundles/atomos.tests.testbundles.service.impl.activator/src/main/java/org/apache/felix/atomos/tests/testbundles/service/impl/activator/Activator.java => atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b4/ActivatorBundle4.java} (52%)
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/META-INF/MANIFEST.MF
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/bundle.1-1.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/bundle.1-2.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/common.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/META-INF/MANIFEST.MF
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/bundle.2-1.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/bundle.2-2.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/common.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/META-INF/MANIFEST.MF
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/bundle.3-1.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/bundle.3-2.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/common.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/META-INF/MANIFEST.MF
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/bundle.4-1.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/bundle.4-2.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/common.txt
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/bundles.index
 create mode 100644 atomos.tests/atomos.tests.index.bundles/src/test/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunchTest.java


[felix-atomos] 01/02: FELIX-6227 - Generalize Atomos index to be used independent of substrate

Posted by tj...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tjwatson pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-atomos.git

commit 8925fd8c5915279769df040cf42d46ad9f8ca22d
Author: Thomas Watson <tj...@us.ibm.com>
AuthorDate: Fri Feb 28 09:31:24 2020 -0600

    FELIX-6227 - Generalize Atomos index to be used independent of substrate
    
    The substrate support for Atomos has a concept of an
    atomos/bundles.index resource which can be read at runtime to figure out
    what bundles are available and what resources are available for bundle
    entries.
    
    This commit refactors that support to be general such that this resource
    can be used for loading Atomos content from any environment where the
    resource atomos/bundles.index can be found by the Atomos runtime. This
    allows for other cases similar to substrate where the original bundle
    JARs may no longer be around as artifacts at runtime.  For example, with
    Android applications.
---
 .gitignore                                         |   1 -
 README.md                                          |   6 +-
 .../atomos.examples.substrate.equinox/pom.xml      |   2 +-
 .../atomos.examples.substrate.felix/pom.xml        |   2 +-
 .../impl/runtime/base/AtomosRuntimeBase.java       | 430 ++++++++++++++++++---
 .../impl/runtime/base/AtomosRuntimeClassPath.java  |  10 +-
 .../content/ConnectContentCloseableJar.java        |  89 +++++
 .../ConnectContentFile.java}                       |   6 +-
 .../ConnectContentIndexed.java}                    |  14 +-
 .../ConnectContentJar.java}                        |  27 +-
 .../{substrate => content}/package-info.java       |   2 +-
 .../impl/runtime/modules/AtomosRuntimeModules.java |  15 +-
 ...nnectContent.java => ConnectContentModule.java} |   4 +-
 .../runtime/substrate/AtomosRuntimeSubstrate.java  | 430 ---------------------
 .../substrate/SubstrateJarConnectContent.java      | 139 -------
 .../apache/felix/atomos/launch/AtomosLauncher.java |   2 +-
 .../apache/felix/atomos/runtime/AtomosRuntime.java |  23 +-
 atomos.tests/atomos.tests.index.bundles/pom.xml    |  28 ++
 .../atomos/tests/index/bundles/IndexLaunch.java    |  43 +++
 .../tests/index/bundles/b1/ActivatorBundle1.java   |  37 ++
 .../tests/index/bundles/b2/ActivatorBundle2.java   |  37 ++
 .../tests/index/bundles/b3/ActivatorBundle3.java   |  37 ++
 .../tests/index/bundles/b4/ActivatorBundle4.java   |  37 ++
 .../main/resources/atomos/1/META-INF/MANIFEST.MF   |   5 +
 .../resources/atomos/1/OSGI-INF/bundle.1-1.txt     |   1 +
 .../resources/atomos/1/OSGI-INF/bundle.1-2.txt     |   1 +
 .../main/resources/atomos/1/OSGI-INF/common.txt    |   1 +
 .../main/resources/atomos/2/META-INF/MANIFEST.MF   |   5 +
 .../resources/atomos/2/OSGI-INF/bundle.2-1.txt     |   1 +
 .../resources/atomos/2/OSGI-INF/bundle.2-2.txt     |   1 +
 .../main/resources/atomos/2/OSGI-INF/common.txt    |   1 +
 .../main/resources/atomos/3/META-INF/MANIFEST.MF   |   5 +
 .../resources/atomos/3/OSGI-INF/bundle.3-1.txt     |   1 +
 .../resources/atomos/3/OSGI-INF/bundle.3-2.txt     |   1 +
 .../main/resources/atomos/3/OSGI-INF/common.txt    |   1 +
 .../main/resources/atomos/4/META-INF/MANIFEST.MF   |   5 +
 .../resources/atomos/4/OSGI-INF/bundle.4-1.txt     |   1 +
 .../resources/atomos/4/OSGI-INF/bundle.4-2.txt     |   1 +
 .../main/resources/atomos/4/OSGI-INF/common.txt    |   1 +
 .../src/main/resources/atomos/bundles.index        |  36 ++
 .../tests/index/bundles/IndexLaunchTest.java       | 183 +++++++++
 atomos.tests/pom.xml                               |   1 +
 42 files changed, 1013 insertions(+), 660 deletions(-)

diff --git a/.gitignore b/.gitignore
index 43e926a..01c54ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,7 +19,6 @@
 .springBeans
 /build
 /code
-MANIFEST.MF
 _site/
 activemq-data
 bin
diff --git a/README.md b/README.md
index f07dc31..385c1a0 100644
--- a/README.md
+++ b/README.md
@@ -91,8 +91,8 @@ Note that `install` target must be used so that Atomos is installed into your lo
 
 This will create a `target/atomos` executable in each substrate example project. If you launch `atomos` it will give you a gogo `g!` prompt to run gogo commands.  Also included in this example is a version of the Felix web console.  The web console can be access with http://localhost:8080/system/console/bundles and the id/password is admin/admin.
 
-For the Felix and Equinox example a directory `target/substrate_lib/` is created.  This contains all the original bundle JARs that got compiled into the native image `atomos`.  In order to launch the native `atomos` you must be in the directory containing both `atomos` and the `substrate_lib/` folder.  This is a simple way for Atomos to discover the available bundlesand load additional bundle entries at runtime.
+For the Felix and Equinox example a directory `target/atomos_lib/` is created.  This contains all the original bundle JARs that got compiled into the native image `atomos`.  In order to launch the native `atomos` you must be in the directory containing both `atomos` and the `atomos_lib/` folder.  This is a simple way for Atomos to discover the available bundles and load additional bundle entries at runtime.
 
-Alternatively a substrate image can be created that does not rely on the directory `target/substrate_lib/` to discover the bundles.  Instead the bundle entry resources can be placed in an `atomos/` folder which is placed on the classpath during native image compilation. The resources from the `atomos/` folder can then be included in the native image.  The `atomos/` folder has a file `bundles.index` that contains information for Atomos to discover the bundles and their entries that are in [...]
+Alternatively a substrate image can be created that does not rely on the directory `target/atomos_lib/` to discover the bundles.  Instead the bundle entry resources can be placed in an `atomos/` folder which is placed on the classpath during native image compilation. The resources from the `atomos/` folder can then be included in the native image.  The `atomos/` folder has a file `bundles.index` that contains information for Atomos to discover the bundles and their entries that are inclu [...]
 
-If substrate adds full introspection to the Java Platform Module System in the future it could allow Atomos to discover the modules within the image and load them as bundles.  If a proper module reader could be obtained and contain the necessary resources from the original bundle JARs then it would eliminate the need for the `substrate_lib/` or `atomos/` resource folder.
+If substrate adds full introspection to the Java Platform Module System in the future it could allow Atomos to discover the modules within the image and load them as bundles.  If a proper module reader could be obtained and contain the necessary resources from the original bundle JARs then it would eliminate the need for the `atomos_lib/` or `atomos/` resource folder.
diff --git a/atomos.examples/atomos.examples.substrate.equinox/pom.xml b/atomos.examples/atomos.examples.substrate.equinox/pom.xml
index 41f14fe..2cba513 100644
--- a/atomos.examples/atomos.examples.substrate.equinox/pom.xml
+++ b/atomos.examples/atomos.examples.substrate.equinox/pom.xml
@@ -176,7 +176,7 @@
                             <goal>copy-dependencies</goal>
                         </goals>
                         <configuration>
-                            <outputDirectory>${project.build.directory}/substrate_lib</outputDirectory>
+                            <outputDirectory>${project.build.directory}/atomos_lib</outputDirectory>
                             <overWriteReleases>false</overWriteReleases>
                             <overWriteSnapshots>false</overWriteSnapshots>
                             <overWriteIfNewer>true</overWriteIfNewer>
diff --git a/atomos.examples/atomos.examples.substrate.felix/pom.xml b/atomos.examples/atomos.examples.substrate.felix/pom.xml
index 825c1bf..c9d4e53 100644
--- a/atomos.examples/atomos.examples.substrate.felix/pom.xml
+++ b/atomos.examples/atomos.examples.substrate.felix/pom.xml
@@ -190,7 +190,7 @@
                             <goal>copy-dependencies</goal>
                         </goals>
                         <configuration>
-                            <outputDirectory>${project.build.directory}/substrate_lib</outputDirectory>
+                            <outputDirectory>${project.build.directory}/atomos_lib</outputDirectory>
                             <overWriteReleases>false</overWriteReleases>
                             <overWriteSnapshots>false</overWriteSnapshots>
                             <overWriteIfNewer>true</overWriteIfNewer>
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/AtomosRuntimeBase.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/AtomosRuntimeBase.java
index a73a8d8..b4d9ce7 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/AtomosRuntimeBase.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/AtomosRuntimeBase.java
@@ -13,8 +13,10 @@
  */
 package org.apache.felix.atomos.impl.runtime.base;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.UncheckedIOException;
 import java.net.JarURLConnection;
 import java.net.URISyntaxException;
@@ -35,6 +37,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -43,43 +46,65 @@ import java.util.jar.JarFile;
 import java.util.jar.Manifest;
 
 import org.apache.felix.atomos.impl.runtime.base.AtomosRuntimeBase.AtomosLayerBase.AtomosContentBase;
-import org.apache.felix.atomos.impl.runtime.substrate.AtomosRuntimeSubstrate;
+import org.apache.felix.atomos.impl.runtime.base.AtomosRuntimeBase.AtomosLayerBase.AtomosContentIndexed;
+import org.apache.felix.atomos.impl.runtime.content.ConnectContentCloseableJar;
+import org.apache.felix.atomos.impl.runtime.content.ConnectContentFile;
+import org.apache.felix.atomos.impl.runtime.content.ConnectContentIndexed;
+import org.apache.felix.atomos.impl.runtime.content.ConnectContentJar;
 import org.apache.felix.atomos.runtime.AtomosContent;
 import org.apache.felix.atomos.runtime.AtomosLayer;
 import org.apache.felix.atomos.runtime.AtomosLayer.LoaderType;
 import org.apache.felix.atomos.runtime.AtomosRuntime;
-import org.osgi.framework.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.framework.Version;
 import org.osgi.framework.connect.ConnectContent;
 import org.osgi.framework.connect.ConnectFrameworkFactory;
 import org.osgi.framework.connect.FrameworkUtilHelper;
 import org.osgi.framework.connect.ModuleConnector;
 import org.osgi.framework.hooks.bundle.CollisionHook;
 import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+import org.osgi.framework.namespace.PackageNamespace;
 import org.osgi.framework.wiring.BundleCapability;
 import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.FrameworkWiring;
 
+import sun.misc.Signal;
+
 public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBundleListener, FrameworkUtilHelper
 {
     static final String JAR_PROTOCOL = "jar";
     static final String FILE_PROTOCOL = "file";
-    public static final String ATOMOS_DEBUG_PROP = "atomos.enable.debug";
+    public static final String ATOMOS_PROP_PREFIX = "atomos.";
+    public static final String ATOMOS_DEBUG_PROP = ATOMOS_PROP_PREFIX + "enable.debug";
+    public static final String ATOMOS_LOAD_INDEX_PROP = ATOMOS_PROP_PREFIX + "load.index";
     public static final String ATOMOS_BUNDLES = "/atomos/";
     public static final String ATOMOS_BUNDLES_INDEX = ATOMOS_BUNDLES + "bundles.index";
-    public static final String ATOMOS_SUBSTRATE = "atomos.substrate";
-    public static final String ATOMOS_RUNTIME_CLASS = "atomos.runtime.class";
+    public static final String ATOMOS_BUNDLE = "ATOMOS_BUNDLE";
+    public static final String ATOMOS_LIB_DIR_PROP = ATOMOS_PROP_PREFIX + "lib.dir";
+    public static final String ATOMOS_RUNTIME_CLASS_PROP = ATOMOS_PROP_PREFIX
+        + "runtime.class";
     public static final String ATOMOS_RUNTIME_MODULES_CLASS = "org.apache.felix.atomos.impl.runtime.modules.AtomosRuntimeModules";
-    public static final String SUBSTRATE_LIB_DIR = "substrate_lib";
+    public static final String ATOMOS_LIB_DIR = "atomos_lib";
     public static final String GRAAL_NATIVE_IMAGE_KIND = "org.graalvm.nativeimage.kind";
 
     private final boolean DEBUG;
+    private final Index indexType;
 
     private final AtomicReference<BundleContext> context = new AtomicReference<>();
     private final AtomicReference<File> storeRoot = new AtomicReference<>();
 
+    private ServiceRegistration<?> atomosCommandsReg = null;
+    protected final Map<String, String> config = new ConcurrentHashMap<String, String>();
+
     private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
 
-    // The following area all protected by the read/write lock
+    // The following are all protected by the read/write lock
 
     // A map of Atomos contents that have a connect location; the key is the connect location
     private final Map<String, AtomosContentBase> connectLocationToAtomosContent = new HashMap<>();
@@ -94,55 +119,49 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
     private final Map<AtomosContent, String> atomosContentToConnectLocation = new HashMap<>();
     // A set of connect locations that the framework has connected using the AtomosModuleConnector
     private final Map<String, AtomosContentBase> connectedLocations = new HashMap<>();
+    // A map of indexed content; key is package name
+    private final Map<String, AtomosContentIndexed> packageToAtomosContent = new ConcurrentHashMap<>();
 
     protected final AtomicLong nextLayerId = new AtomicLong(0);
 
-    ServiceRegistration<?> atomosCommandsReg = null;
+    public static enum Index
+    {
+        IGNORE, FIRST
+    }
 
-    public static AtomosRuntime newAtomosRuntime()
+    public static AtomosRuntime newAtomosRuntime(Map<String, String> config)
     {
-        String runtimeClass = System.getProperty(ATOMOS_RUNTIME_CLASS);
+        String runtimeClass = config.get(ATOMOS_RUNTIME_CLASS_PROP);
         if (runtimeClass != null)
         {
-            return loadRuntime(runtimeClass);
+            return loadRuntime(runtimeClass, config);
         }
-        if (System.getProperty(ATOMOS_SUBSTRATE) != null
+        if (config.get(
+            ATOMOS_LIB_DIR_PROP) != null
             || System.getProperty(GRAAL_NATIVE_IMAGE_KIND) != null)
         {
-            URL index = AtomosRuntimeBase.class.getResource(ATOMOS_BUNDLES_INDEX);
-            if (index != null)
-            {
-                return new AtomosRuntimeSubstrate(null);
-            }
-            File substrateLibDir = findSubstrateLibDir();
-            if (substrateLibDir.isDirectory())
-            {
-                return new AtomosRuntimeSubstrate(substrateLibDir);
-            }
-            else
-            {
-                throw new IllegalStateException("No substrate_lib directory found.");
-            }
+            return new AtomosRuntimeClassPath(config);
         }
         try
         {
             Class.forName("java.lang.Module");
-            return loadRuntime(ATOMOS_RUNTIME_MODULES_CLASS);
+            return loadRuntime(ATOMOS_RUNTIME_MODULES_CLASS, config);
         }
         catch (ClassNotFoundException e)
         {
             // ignore
         }
         // default to classpath
-        return new AtomosRuntimeClassPath();
+        return new AtomosRuntimeClassPath(config);
     }
 
-    private static AtomosRuntime loadRuntime(String runtimeClass)
+    private static AtomosRuntime loadRuntime(String runtimeClass,
+        Map<String, String> config)
     {
         try
         {
             return (AtomosRuntimeBase) Class.forName(
-                runtimeClass).getConstructor().newInstance();
+                runtimeClass).getConstructor(Map.class).newInstance(config);
         }
         catch (Exception e)
         {
@@ -151,15 +170,30 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
         }
     }
 
-    public static File findSubstrateLibDir()
+    public File findAtomosLibDir()
     {
-        String substrateProp = System.getProperty(ATOMOS_SUBSTRATE);
-        return new File(substrateProp, SUBSTRATE_LIB_DIR);
+        String libDirProp = config.get(ATOMOS_LIB_DIR_PROP);
+        return new File(libDirProp, ATOMOS_LIB_DIR);
     }
 
-    protected AtomosRuntimeBase()
+    protected AtomosRuntimeBase(Map<String, String> config)
     {
-        DEBUG = Boolean.getBoolean(ATOMOS_DEBUG_PROP);
+        saveConfig(config);
+        DEBUG = Boolean.parseBoolean(this.config.get(ATOMOS_DEBUG_PROP));
+        String sType = config.get(ATOMOS_LOAD_INDEX_PROP);
+        indexType = sType != null ? Index.valueOf(sType)
+            : (System.getProperty(GRAAL_NATIVE_IMAGE_KIND) != null ? Index.FIRST
+                : Index.IGNORE);
+        try
+        {
+            // substrate native image does not run shutdown hooks on ctrl-c
+            // this works around it by using our own signal handler
+            Signal.handle(new Signal("INT"), sig -> System.exit(0));
+        }
+        catch (Throwable t)
+        {
+            // do nothing if Signal isn't available
+        }
     }
 
     protected final void lockWrite()
@@ -469,10 +503,11 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
         {
             case BundleEvent.INSTALLED:
             case BundleEvent.UPDATED :
+                addPackages(event.getBundle());
                 AtomosContent content = getByConnectLocation(location, true);
                 if (content != null)
                 {
-                    debug("Bundle successfullly connected %s", content);
+                    debug("Bundle successfully connected %s", content);
                     connectionManaged = managingConnected.get().removeLastOccurrence(
                         content);
                 }
@@ -589,11 +624,25 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
             children.remove(child);
         }
 
-        protected Set<AtomosContentBase> findClassPathAtomosContents()
+        protected final Set<AtomosContentBase> findAtomosContents()
         {
-            // first get the boot modules
             Set<AtomosContentBase> bootBundles = new HashSet<>();
-            findBootLayerAtomosContents(bootBundles);
+
+            // first get the modules from the boot ModuleLayer (Java 9+ JPMS)
+            findBootModuleLayerAtomosContents(bootBundles);
+            // for class path mode we look for manifests on the classpath
+            findAtomosContentsByClassLoaderManifests(bootBundles);
+            // finally find by Atomos index, note that Atomos index content will override duplicates
+            findAtomosIndexedContents(bootBundles);
+
+            return Collections.unmodifiableSet(bootBundles);
+        }
+
+        protected abstract void findBootModuleLayerAtomosContents(
+            Set<AtomosContentBase> result);
+
+        void findAtomosContentsByClassLoaderManifests(Set<AtomosContentBase> result)
+        {
             try
             {
                 ClassLoader cl = getClass().getClassLoader();
@@ -636,13 +685,14 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
                             URL url;
                             if (content instanceof File)
                             {
-                                connectContent = new FileConnectContent((File) content);
+                                connectContent = new ConnectContentFile((File) content);
                                 url = ((File) content).toURI().toURL();
 
                             }
                             else
                             {
-                                connectContent = new JarConnectContent((JarFile) content);
+                                connectContent = new ConnectContentJar(
+                                    () -> ((JarFile) content), null);
                                 url = new File(
                                     ((JarFile) content).getName()).toURI().toURL();
                             }
@@ -666,8 +716,8 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
                             Version version = Version.parseVersion(
                                 headers.getValue(Constants.BUNDLE_VERSION));
 
-                            bootBundles.add(new AtomosContentClassPath(location,
-                                symbolicName, version, connectContent, url));
+                            result.add(new AtomosContentClassPath(location, symbolicName,
+                                version, connectContent, url));
                         }
                     }
                 }
@@ -676,12 +726,168 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
             {
                 throw new IllegalStateException("Error finding class path bundles.", e);
             }
-            return Collections.unmodifiableSet(bootBundles);
         }
 
-        protected abstract void findBootLayerAtomosContents(
-            Set<AtomosContentBase> result);
+        private void findAtomosIndexedContents(Set<AtomosContentBase> bootBundles)
+        {
+            URL index = Index.FIRST == indexType
+                ? getClass().getResource(ATOMOS_BUNDLES_INDEX)
+                : null;
+            debug("Atomos index url: %s", index);
+            if (index != null)
+            {
+                findAtomosIndexedContent(index, bootBundles);
+            }
+            else
+            {
+                File atomosLibDir = findAtomosLibDir();
+                if (atomosLibDir.isDirectory())
+                {
+                    findAtomosLibIndexedContent(bootBundles, atomosLibDir);
+                }
+            }
+        }
 
+        private void findAtomosLibIndexedContent(Set<AtomosContentBase> bootBundles,
+            File atomosLibDir)
+        {
+            for (File f : atomosLibDir.listFiles())
+            {
+                if (f.isFile())
+                {
+                    try (JarFile jar = new JarFile(f))
+                    {
+                        Attributes headers = jar.getManifest().getMainAttributes();
+                        String symbolicName = headers.getValue(
+                            Constants.BUNDLE_SYMBOLICNAME);
+                        if (symbolicName != null)
+                        {
+                            int semiColon = symbolicName.indexOf(';');
+                            if (semiColon != -1)
+                            {
+                                symbolicName = symbolicName.substring(0, semiColon);
+                            }
+                            symbolicName = symbolicName.trim();
+
+                            ConnectContent connectContent = new ConnectContentCloseableJar(
+                                f.getName(), () -> atomosLibDir);
+                            connectContent.open();
+                            String location;
+                            if (connectContent.getEntry(
+                                "META-INF/services/org.osgi.framework.launch.FrameworkFactory").isPresent())
+                            {
+                                location = Constants.SYSTEM_BUNDLE_LOCATION;
+                            }
+                            else
+                            {
+                                location = f.getName();
+                                if (!getName().isEmpty())
+                                {
+                                    location = getName() + ":" + location;
+                                }
+                            }
+                            Version version = Version.parseVersion(
+                                headers.getValue(Constants.BUNDLE_VERSION));
+                            AtomosContentBase bundle = new AtomosContentIndexed(location,
+                                symbolicName, version, connectContent);
+                            bootBundles.add(bundle);
+                        }
+                    }
+                    catch (IOException e)
+                    {
+                        // ignore and continue
+                    }
+                }
+            }
+        }
+
+        private void findAtomosIndexedContent(URL index,
+            Set<AtomosContentBase> bootBundles)
+        {
+
+            try (BufferedReader reader = new BufferedReader(
+                new InputStreamReader(index.openStream())))
+            {
+                String currentIndex = null;
+                String currentBSN = null;
+                Version currentVersion = null;
+                List<String> currentPaths = null;
+                String line = reader.readLine();
+                if (line != null)
+                {
+                    do
+                    {
+                        if (ATOMOS_BUNDLE.equals(line))
+                        {
+                            if (currentIndex != null)
+                            {
+                                ConnectContentIndexed content = new ConnectContentIndexed(
+                                    currentIndex, currentPaths);
+                                bootBundles.add(new AtomosContentIndexed(
+                                    getIndexedLocation(content, currentBSN), currentBSN,
+                                    currentVersion, content));
+                                debug("Found indexed content: %s %s %s %s", currentIndex,
+                                    currentBSN, currentVersion, currentPaths);
+                            }
+                            currentIndex = null;
+                            currentBSN = null;
+                            currentVersion = null;
+                            currentPaths = new ArrayList<>();
+                        }
+                        else
+                        {
+                            if (currentIndex == null)
+                            {
+                                currentIndex = line;
+                            }
+                            else if (currentBSN == null)
+                            {
+                                currentBSN = line;
+                            }
+                            else if (currentVersion == null)
+                            {
+                                currentVersion = Version.valueOf(line);
+                            }
+                            else
+                            {
+                                currentPaths.add(line);
+                            }
+                        }
+                    }
+                    while ((line = reader.readLine()) != null);
+                    if (currentIndex != null)
+                    {
+                        ConnectContentIndexed content = new ConnectContentIndexed(
+                            currentIndex, currentPaths);
+                        bootBundles.add(new AtomosContentIndexed(
+                            getIndexedLocation(content, currentBSN), currentBSN,
+                            currentVersion, content));
+                        debug("Found indexed content: %s %s %s %s", currentIndex,
+                            currentBSN, currentVersion, currentPaths);
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        private static final String FWK_FACTORY_SERVICE = "META-INF/services/org.osgi.framework.launch.FrameworkFactory";
+
+        private String getIndexedLocation(ConnectContent content, String location)
+        {
+            return content.getEntry(FWK_FACTORY_SERVICE).map(
+                e -> Constants.SYSTEM_BUNDLE_LOCATION).orElseGet(() -> {
+                    String qualified = location;
+                    ;
+                    if (!getName().isEmpty())
+                    {
+                        qualified = getName() + ":" + location;
+                    }
+                    return qualified;
+                });
+        }
         /**
          * Returns the bundle content that contains the specified manifest URL.
          * The return type will be a JarFile or a File for an exploded bundle.
@@ -1097,6 +1303,26 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
                 return contentURL;
             }
         }
+
+        /**
+         * Atomos content discovered in an Atomos index.  The key is this content itself
+         * which is used to lookup the content based on package name.
+         */
+        public class AtomosContentIndexed extends AtomosContentBase
+        {
+
+            public AtomosContentIndexed(String location, String symbolicName, Version version, ConnectContent content)
+            {
+                super(location, symbolicName, version, content);
+            }
+
+            @Override
+            protected final Object getKey()
+            {
+                // TODO a bit hokey to use ourselves as a key
+                return this;
+            }
+        }
     }
 
     @Override
@@ -1129,6 +1355,15 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
 
     protected Object getAtomosKey(Class<?> classFromBundle)
     {
+        Package pkg = classFromBundle.getPackage();
+        if (pkg != null)
+        {
+            AtomosContentIndexed indexed = packageToAtomosContent.get(pkg.getName());
+            if (indexed != null)
+            {
+                return indexed;
+            }
+        }
         return classFromBundle.getProtectionDomain().getCodeSource().getLocation();
     }
 
@@ -1205,6 +1440,10 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
         AtomosFrameworkUtilHelper.addHelper(this);
 
         bc.addBundleListener(this);
+        for (Bundle b : bc.getBundles())
+        {
+            addPackages(b);
+        }
 
         AtomosFrameworkHooks hooks = new AtomosFrameworkHooks(this);
         bc.registerService(ResolverHookFactory.class, hooks, null);
@@ -1219,6 +1458,51 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
         atomosCommandsReg = new AtomosCommands(this).register(bc);
     }
 
+    void addPackages(Bundle b)
+    {
+        AtomosContentBase atomosContent = (AtomosContentBase) getConnectedContent(
+            b.getLocation());
+        if (atomosContent instanceof AtomosContentIndexed)
+        {
+            BundleRevision r = b.adapt(BundleRevision.class);
+            r.getDeclaredCapabilities(PackageNamespace.PACKAGE_NAMESPACE).forEach(
+                (p) -> packageToAtomosContent.putIfAbsent(
+                    (String) p.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE),
+                    (AtomosContentIndexed) atomosContent));
+            String privatePackages = b.getHeaders("").get("Private-Package");
+            if (privatePackages != null)
+            {
+                for (String pkgName : privatePackages.split(","))
+                {
+                    pkgName = pkgName.trim();
+                    packageToAtomosContent.put(pkgName,
+                        (AtomosContentIndexed) atomosContent);
+                }
+            }
+            else
+            {
+                // ensure content is open
+                b.getEntry("does.not.exist");
+                ConnectContent content = atomosContent.getConnectContent();
+                try
+                {
+                    content.getEntries().forEach((s) -> {
+                        if (s.length() > 1 && s.endsWith("/") && s.indexOf('-') < 0)
+                        {
+                            String pkg = s.substring(0, s.length() - 1).replace('/', '.');
+                            packageToAtomosContent.put(pkg,
+                                (AtomosContentIndexed) atomosContent);
+                        }
+                    });
+                }
+                catch (IOException e)
+                {
+                    // ignore
+                    debug("IOException getting entries: %s", e.getMessage());
+                }
+            }
+        }
+    }
     protected void stop(BundleContext bc) throws BundleException
     {
         debug("Stopping Atomos runtime");
@@ -1252,19 +1536,29 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
 
     private void installAtomosContents(AtomosLayer atomosLayer,
         boolean installBundles,
-        boolean startBundles) throws BundleException
+        boolean startBundles)
     {
         if (installBundles)
         {
+            debug("Installing Atomos content.");
             List<Bundle> bundles = new ArrayList<>();
             for (AtomosContent atomosContent : atomosLayer.getAtomosContents())
             {
                 if (getBundle(atomosContent) == null)
                 {
-                    Bundle b = atomosContent.install("atomos");
-                    if (b != null && b.getBundleId() != 0)
+                    debug("Installing AtomosContent: %s", atomosContent);
+                    try
+                    {
+                        Bundle b = atomosContent.install("atomos");
+                        if (b != null && b.getBundleId() != 0)
+                        {
+                            bundles.add(b);
+                        }
+                    }
+                    catch (BundleException e)
                     {
-                        bundles.add(b);
+                        debug("Failed to install to install %s: %s", atomosContent,
+                            e.getMessage());
                     }
                 }
             }
@@ -1272,10 +1566,20 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
             {
                 for (Bundle b : bundles)
                 {
+                    debug("Starting connected bundle: %s", b);
                     BundleRevision rev = b.adapt(BundleRevision.class);
                     if ((rev.getTypes() & BundleRevision.TYPE_FRAGMENT) == 0)
                     {
-                        b.start();
+                        try
+                        {
+                            b.start();
+                        }
+                        catch (BundleException e)
+                        {
+                            debug("Failed to install to install %s: %s", e,
+                                b,
+                                e.getMessage());
+                        }
                     }
                 }
                 for (AtomosLayer child : atomosLayer.getChildren())
@@ -1288,6 +1592,7 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
 
     public void initialize(File storage, Map<String, String> configuration)
     {
+        saveConfig(configuration);
         if (!storeRoot.compareAndSet(null, storage))
         {
             throw new IllegalStateException(
@@ -1303,6 +1608,23 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
         }
     }
 
+    private void saveConfig(Map<String, String> configuration) {
+        configuration.forEach((k,v) -> {
+            if (k != null && k.startsWith(ATOMOS_PROP_PREFIX) && v != null)
+            {
+                config.put(k, v);
+            }
+        });
+    }
+
+    public void debug(String message, BundleException e, Object... args)
+    {
+        debug(message, args);
+        if (DEBUG)
+        {
+            e.printStackTrace();
+        }
+    }
     public void debug(String message, Object... args)
     {
         if (DEBUG)
@@ -1317,4 +1639,10 @@ public abstract class AtomosRuntimeBase implements AtomosRuntime, SynchronousBun
             }
         }
     }
+
+    @SuppressWarnings("unchecked")
+    public static <E extends Throwable> void sneakyThrow(Throwable e) throws E
+    {
+        throw (E) e;
+    }
 }
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/AtomosRuntimeClassPath.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/AtomosRuntimeClassPath.java
index f7fa06b..efabe8f 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/AtomosRuntimeClassPath.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/AtomosRuntimeClassPath.java
@@ -19,6 +19,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.Set;
 
@@ -33,6 +34,11 @@ public class AtomosRuntimeClassPath extends AtomosRuntimeBase
 
     private final AtomosLayer bootLayer = createBootLayer();
 
+    public AtomosRuntimeClassPath(Map<String, String> config)
+    {
+        super(config);
+    }
+
     private AtomosLayer createBootLayer()
     {
         lockWrite();
@@ -83,7 +89,7 @@ public class AtomosRuntimeClassPath extends AtomosRuntimeBase
         protected AtomosLayerClassPath(List<AtomosLayer> parents, long id, String name, LoaderType loaderType, Path... paths)
         {
             super(parents, id, name, loaderType, paths);
-            atomosContents = findClassPathAtomosContents();
+            atomosContents = findAtomosContents();
         }
 
         @Override
@@ -93,7 +99,7 @@ public class AtomosRuntimeClassPath extends AtomosRuntimeBase
         }
 
         @Override
-        protected void findBootLayerAtomosContents(Set<AtomosContentBase> result)
+        protected void findBootModuleLayerAtomosContents(Set<AtomosContentBase> result)
         {
             // do nothing for class path runtime case
         }
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentCloseableJar.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentCloseableJar.java
new file mode 100644
index 0000000..0a13fac
--- /dev/null
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentCloseableJar.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.atomos.impl.runtime.content;
+
+import static org.apache.felix.atomos.impl.runtime.base.AtomosRuntimeBase.sneakyThrow;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.zip.ZipFile;
+
+public class ConnectContentCloseableJar extends ConnectContentJar
+{
+
+
+    static class ZipFileHolder implements Supplier<ZipFile>, Consumer<Supplier<ZipFile>>
+    {
+        private final Supplier<File> rootSupplier;
+        private final String fileName;
+
+        public ZipFileHolder(String fileName, Supplier<File> rootSupplier)
+        {
+            this.fileName = fileName;
+            this.rootSupplier = rootSupplier;
+        }
+
+        private volatile ZipFile zipFile;
+
+        @Override
+        public void accept(Supplier<ZipFile> s)
+        {
+            if (s != this)
+            {
+                return;
+            }
+            ZipFile current = zipFile;
+            if (current != null)
+            {
+                zipFile = null;
+                try
+                {
+                    current.close();
+                }
+                catch (IOException e)
+                {
+                    sneakyThrow(e);
+                }
+            }
+        }
+
+        @Override
+        public ZipFile get()
+        {
+            ZipFile current = zipFile;
+            if (current == null)
+            {
+                try
+                {
+                    current = zipFile = new ZipFile(
+                        new File(rootSupplier.get(), fileName));
+                }
+                catch (IOException e)
+                {
+                    sneakyThrow(e);
+                }
+            }
+            return current;
+        }
+
+    }
+
+    public ConnectContentCloseableJar(String fileName, Supplier<File> rootSupplier)
+    {
+        super(new ZipFileHolder(fileName, rootSupplier),
+            z -> ((ZipFileHolder) z).accept(z));
+    }
+}
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/FileConnectContent.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentFile.java
similarity index 96%
rename from atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/FileConnectContent.java
rename to atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentFile.java
index 3f422e4..920fc41 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/FileConnectContent.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentFile.java
@@ -11,7 +11,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.felix.atomos.impl.runtime.base;
+package org.apache.felix.atomos.impl.runtime.content;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -25,7 +25,7 @@ import java.util.stream.Collectors;
 
 import org.osgi.framework.connect.ConnectContent;
 
-public class FileConnectContent implements ConnectContent
+public class ConnectContentFile implements ConnectContent
 {
     public static class FileConnectEntry implements ConnectEntry
     {
@@ -77,7 +77,7 @@ public class FileConnectContent implements ConnectContent
 
     final File root;
 
-    public FileConnectContent(File root)
+    public ConnectContentFile(File root)
     {
         this.root = root;
     }
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/SubstrateIndexConnectContent.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentIndexed.java
similarity index 88%
rename from atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/SubstrateIndexConnectContent.java
rename to atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentIndexed.java
index 631473c..4f8701a 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/SubstrateIndexConnectContent.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentIndexed.java
@@ -11,20 +11,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.felix.atomos.impl.runtime.substrate;
+package org.apache.felix.atomos.impl.runtime.content;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
 import org.apache.felix.atomos.impl.runtime.base.AtomosRuntimeBase;
 import org.osgi.framework.connect.ConnectContent;
 
-public class SubstrateIndexConnectContent implements ConnectContent
+public class ConnectContentIndexed implements ConnectContent
 {
     static class URLConnectEntry implements ConnectEntry
     {
@@ -77,13 +79,13 @@ public class SubstrateIndexConnectContent implements ConnectContent
 
     }
 
-    final String index;
-    final List<String> entries;
+    private final String index;
+    private final Set<String> entries;
 
-    SubstrateIndexConnectContent(String index, List<String> entries)
+    public ConnectContentIndexed(String index, List<String> entries)
     {
         this.index = index;
-        this.entries = Collections.unmodifiableList(entries);
+        this.entries = Collections.unmodifiableSet(new LinkedHashSet<>(entries));
     }
 
     @Override
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/JarConnectContent.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentJar.java
similarity index 76%
rename from atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/JarConnectContent.java
rename to atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentJar.java
index c1c80b3..1e144bf 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/base/JarConnectContent.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/ConnectContentJar.java
@@ -11,7 +11,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.felix.atomos.impl.runtime.base;
+package org.apache.felix.atomos.impl.runtime.content;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -19,30 +19,37 @@ import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import org.osgi.framework.connect.ConnectContent;
 
-public class JarConnectContent implements ConnectContent
+public class ConnectContentJar implements ConnectContent
 {
-    final ZipFile zipFile;
+    final Supplier<ZipFile> zipSupplier;
+    final Consumer<Supplier<ZipFile>> closer;
 
-    public JarConnectContent(ZipFile zipFile)
+    public ConnectContentJar(Supplier<ZipFile> zipSupplier, Consumer<Supplier<ZipFile>> closer)
     {
-        this.zipFile = zipFile;
+        this.zipSupplier = zipSupplier;
+        this.closer = closer;
     }
 
     @Override
     public void open() throws IOException
     {
-        // do nothing
+        zipSupplier.get();
     }
 
     @Override
     public void close() throws IOException
     {
-        // do nothing
+        if (closer != null)
+        {
+            closer.accept(zipSupplier);
+        }
     }
 
     @Override
@@ -56,7 +63,7 @@ public class JarConnectContent implements ConnectContent
     {
         return () -> new Iterator<String>()
         {
-            final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+            final Enumeration<? extends ZipEntry> entries = zipSupplier.get().entries();
 
             @Override
             public boolean hasNext()
@@ -75,7 +82,7 @@ public class JarConnectContent implements ConnectContent
     @Override
     public Optional<ConnectEntry> getEntry(String name)
     {
-        ZipEntry entry = zipFile.getEntry(name);
+        ZipEntry entry = zipSupplier.get().getEntry(name);
         if (entry != null)
         {
             return Optional.of(new JarConnectEntry(entry));
@@ -107,7 +114,7 @@ public class JarConnectContent implements ConnectContent
         @Override
         public InputStream getInputStream() throws IOException
         {
-            return zipFile.getInputStream(entry);
+            return zipSupplier.get().getInputStream(entry);
         }
 
         @Override
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/package-info.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/package-info.java
similarity index 91%
rename from atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/package-info.java
rename to atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/package-info.java
index 90ba6d7..e1eb2b8 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/package-info.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/content/package-info.java
@@ -11,4 +11,4 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.felix.atomos.impl.runtime.substrate;
\ No newline at end of file
+package org.apache.felix.atomos.impl.runtime.content;
\ No newline at end of file
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/AtomosRuntimeModules.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/AtomosRuntimeModules.java
index ca66d41..f7e4610 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/AtomosRuntimeModules.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/AtomosRuntimeModules.java
@@ -69,6 +69,11 @@ public class AtomosRuntimeModules extends AtomosRuntimeBase
     private final Map<Configuration, AtomosLayerBase> byConfig = new HashMap<>();
     private final AtomosLayer bootLayer = createBootLayer();
 
+    public AtomosRuntimeModules(Map<String, String> config)
+    {
+        super(config);
+    }
+
     private AtomosLayer createBootLayer()
     {
         return createAtomosLayer(thisConfig, "boot", -1, LoaderType.SINGLE);
@@ -486,7 +491,7 @@ public class AtomosRuntimeModules extends AtomosRuntimeBase
         {
             super(parents, id, name, loaderType, paths);
             moduleLayer = findModuleLayer(config, parents, loaderType);
-            atomosBundles = findAtomosBundles();
+            atomosBundles = findAtomosLayerContent();
         }
 
         @Override
@@ -528,9 +533,9 @@ public class AtomosRuntimeModules extends AtomosRuntimeBase
             }
         }
 
-        private Set<AtomosContentBase> findAtomosBundles()
+        private Set<AtomosContentBase> findAtomosLayerContent()
         {
-            return moduleLayer == null ? findClassPathAtomosContents()
+            return moduleLayer == null ? findAtomosContents()
                 : findModuleLayerAtomosBundles(moduleLayer);
         }
 
@@ -618,7 +623,7 @@ public class AtomosRuntimeModules extends AtomosRuntimeBase
 
             public AtomosContentModule(ResolvedModule resolvedModule, Module module, String location, String symbolicName, Version version)
             {
-                super(location, symbolicName, version, new ModuleConnectContent(module,
+                super(location, symbolicName, version, new ConnectContentModule(module,
                     resolvedModule.reference(), AtomosRuntimeModules.this));
                 this.module = module;
             }
@@ -648,7 +653,7 @@ public class AtomosRuntimeModules extends AtomosRuntimeBase
         }
 
         @Override
-        protected void findBootLayerAtomosContents(Set<AtomosContentBase> result)
+        protected void findBootModuleLayerAtomosContents(Set<AtomosContentBase> result)
         {
             result.addAll(findModuleLayerAtomosBundles(ModuleLayer.boot()));
         }
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/ModuleConnectContent.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/ConnectContentModule.java
similarity index 97%
rename from atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/ModuleConnectContent.java
rename to atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/ConnectContentModule.java
index a5add78..10d1fc0 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/ModuleConnectContent.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/modules/ConnectContentModule.java
@@ -25,7 +25,7 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import org.osgi.framework.connect.ConnectContent;
 
-public class ModuleConnectContent implements ConnectContent
+public class ConnectContentModule implements ConnectContent
 {
     final Module module;
     final ModuleReference reference;
@@ -33,7 +33,7 @@ public class ModuleConnectContent implements ConnectContent
     final AtomicReference<Optional<Map<String, String>>> headers = new AtomicReference<>();
     volatile ModuleReader reader = null;
 
-    public ModuleConnectContent(Module module, ModuleReference reference, AtomosRuntimeModules atomosRuntime)
+    public ConnectContentModule(Module module, ModuleReference reference, AtomosRuntimeModules atomosRuntime)
     {
         this.module = module;
         this.reference = reference;
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/AtomosRuntimeSubstrate.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/AtomosRuntimeSubstrate.java
deleted file mode 100644
index 449a200..0000000
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/AtomosRuntimeSubstrate.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.felix.atomos.impl.runtime.substrate;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.jar.Attributes;
-import java.util.jar.JarFile;
-
-import org.apache.felix.atomos.impl.runtime.base.AtomosRuntimeBase;
-import org.apache.felix.atomos.runtime.AtomosContent;
-import org.apache.felix.atomos.runtime.AtomosLayer;
-import org.apache.felix.atomos.runtime.AtomosLayer.LoaderType;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.SynchronousBundleListener;
-import org.osgi.framework.Version;
-import org.osgi.framework.connect.ConnectContent;
-import org.osgi.framework.connect.ConnectFrameworkFactory;
-import org.osgi.framework.namespace.PackageNamespace;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleRevision;
-
-import sun.misc.Signal;
-
-/**
- * This is a quick and dirty way to get bundle entry resources to work
- * in a substrate native image.  It basically requires all bundles compiled
- * into the native image to also have a bundle JAR on disk located in the
- * substrate_lib/ folder.  Eventually we need a way to map all the resources
- * from a bundle that are not class resources into the native image.  This is
- * mainly for resources located in META-INF and OSGI-INF folders.
- *
- */
-public class AtomosRuntimeSubstrate extends AtomosRuntimeBase
-{
-    private static final String ATOMOS_BUNDLE = "ATOMOS_BUNDLE";
-    private final File substrateLibDir;
-    private final AtomosLayerSubstrate bootLayer;
-    private final List<SubstrateBundleIndexInfo> indexBundles;
-
-    static class SubstrateBundleIndexInfo
-    {
-        final String index;
-        final String bsn;
-        final Version version;
-        final List<String> entries;
-
-        SubstrateBundleIndexInfo(String index, String bsn, Version version, List<String> entries)
-        {
-            this.index = index;
-            this.bsn = bsn;
-            this.version = version;
-            this.entries = entries;
-        }
-
-    }
-
-    public AtomosRuntimeSubstrate(File substrateLibDir)
-    {
-        List<SubstrateBundleIndexInfo> tmpIndexBundles = Collections.emptyList();
-        if (substrateLibDir == null)
-        {
-            URL index = getClass().getResource(ATOMOS_BUNDLES_INDEX);
-            if (index != null)
-            {
-                tmpIndexBundles = new ArrayList<>();
-                try (BufferedReader reader = new BufferedReader(
-                    new InputStreamReader(index.openStream())))
-                {
-                    String line;
-                    String currentIndex = null;
-                    String currentBSN = null;
-                    Version currentVersion = null;
-                    List<String> currentPaths = null;
-                    while ((line = reader.readLine()) != null)
-                    {
-                        if (ATOMOS_BUNDLE.equals(line))
-                        {
-                            if (currentIndex != null)
-                            {
-                                tmpIndexBundles.add(
-                                    new SubstrateBundleIndexInfo(currentIndex, currentBSN,
-                                        currentVersion, currentPaths));
-                            }
-                            currentIndex = null;
-                            currentBSN = null;
-                            currentVersion = null;
-                            currentPaths = new ArrayList<>();
-                        }
-                        else
-                        {
-                            if (currentIndex == null)
-                            {
-                                currentIndex = line;
-                            }
-                            else if (currentBSN == null)
-                            {
-                                currentBSN = line;
-                            }
-                            else if (currentVersion == null)
-                            {
-                                currentVersion = Version.valueOf(line);
-                            }
-                            else
-                            {
-                                currentPaths.add(line);
-                            }
-                        }
-                    }
-                    if (currentIndex != null)
-                    {
-                        tmpIndexBundles.add(new SubstrateBundleIndexInfo(currentIndex,
-                            currentBSN, currentVersion, currentPaths));
-                    }
-                }
-                catch (IOException e)
-                {
-                    throw new RuntimeException(e);
-                }
-            }
-            else
-            {
-                substrateLibDir = AtomosRuntimeBase.findSubstrateLibDir();
-                if (!substrateLibDir.isDirectory())
-                {
-                    throw new IllegalStateException("No substrate_lib directory found.");
-                }
-            }
-        }
-        this.indexBundles = tmpIndexBundles;
-        this.substrateLibDir = substrateLibDir;
-        this.bootLayer = createBootLayer();
-        try
-        {
-            // substrate native image does not run shutdown hooks on ctrl-c
-            // this works around it by using our own signal handler
-            Signal.handle(new Signal("INT"), sig -> System.exit(0));
-        }
-        catch (Throwable t)
-        {
-            // do nothing if Signal isn't available
-        }
-    }
-
-    private AtomosLayerSubstrate createBootLayer()
-    {
-        lockWrite();
-        try
-        {
-            AtomosLayerSubstrate result = new AtomosLayerSubstrate(
-                Collections.emptyList(), nextLayerId.getAndIncrement(), "boot",
-                LoaderType.SINGLE);
-            addAtomosLayer(result);
-            return result;
-        }
-        finally
-        {
-            unlockWrite();
-        }
-    }
-
-    @Override
-    protected AtomosLayer addLayer(List<AtomosLayer> parents, String name, long id,
-        LoaderType loaderType, Path... paths)
-    {
-        throw new UnsupportedOperationException(
-            "Cannot add module layers when Atomos is not loaded as module.");
-    }
-
-    @Override
-    public ConnectFrameworkFactory findFrameworkFactory()
-    {
-        Iterator<ConnectFrameworkFactory> itr = ServiceLoader.load(
-            ConnectFrameworkFactory.class, getClass().getClassLoader()).iterator();
-        if (itr.hasNext())
-        {
-            return itr.next();
-        }
-        throw new RuntimeException("No Framework implementation found.");
-    }
-
-    @Override
-    protected void filterBasedOnReadEdges(AtomosContent atomosContent,
-        Collection<BundleCapability> candidates)
-    {
-        filterNotVisible(atomosContent, candidates);
-    }
-
-    public class AtomosLayerSubstrate extends AtomosLayerBase implements SynchronousBundleListener
-    {
-        private final Set<AtomosContentBase> atomosContents;
-        private final Map<String, AtomosContentBase> packageToAtomosContent = new ConcurrentHashMap<>();
-
-        protected AtomosLayerSubstrate(List<AtomosLayer> parents, long id, String name, LoaderType loaderType, Path... paths)
-        {
-            super(parents, id, name, loaderType, paths);
-            Set<AtomosContentBase> foundBundles = new HashSet<>();
-            findSubstrateAtomosBundles(foundBundles);
-            atomosContents = Collections.unmodifiableSet(foundBundles);
-        }
-
-        AtomosContentBase getContentByPackage(Class<?> clazz)
-        {
-            Package pkg = clazz.getPackage();
-            if (pkg != null)
-            {
-                return packageToAtomosContent.get(pkg.getName());
-            }
-            return null;
-        }
-
-        @Override
-        public final Set<AtomosContent> getAtomosContents()
-        {
-            return asSet(atomosContents);
-        }
-
-        @Override
-        protected void findBootLayerAtomosContents(Set<AtomosContentBase> result)
-        {
-            // do nothing for class path runtime case
-        }
-
-        void findSubstrateAtomosBundles(Set<AtomosContentBase> bundles)
-        {
-            if (!indexBundles.isEmpty())
-            {
-                indexBundles.forEach((b) -> {
-                    ConnectContent connectContent = new SubstrateIndexConnectContent(
-                        b.index, b.entries);
-                    String location;
-                    if (connectContent.getEntry(
-                        "META-INF/services/org.osgi.framework.launch.FrameworkFactory").isPresent())
-                    {
-                        location = Constants.SYSTEM_BUNDLE_LOCATION;
-                    }
-                    else
-                    {
-                        location = b.bsn;
-                        if (!getName().isEmpty())
-                        {
-                            location = getName() + ":" + location;
-                        }
-                    }
-                    bundles.add(new AtomosContentSubstrate(location, b.bsn, b.version,
-                        connectContent));
-                });
-            }
-            else
-            {
-                for (File f : substrateLibDir.listFiles())
-                {
-                    if (f.isFile())
-                    {
-                        try (JarFile jar = new JarFile(f))
-                        {
-                            Attributes headers = jar.getManifest().getMainAttributes();
-                            String symbolicName = headers.getValue(
-                                Constants.BUNDLE_SYMBOLICNAME);
-                            if (symbolicName != null)
-                            {
-                                int semiColon = symbolicName.indexOf(';');
-                                if (semiColon != -1)
-                                {
-                                    symbolicName = symbolicName.substring(0, semiColon);
-                                }
-                                symbolicName = symbolicName.trim();
-
-                                ConnectContent connectContent = new SubstrateJarConnectContent(
-                                    f.getName(), AtomosRuntimeSubstrate.this);
-                                connectContent.open();
-                                String location;
-                                if (connectContent.getEntry(
-                                    "META-INF/services/org.osgi.framework.launch.FrameworkFactory").isPresent())
-                                {
-                                    location = Constants.SYSTEM_BUNDLE_LOCATION;
-                                }
-                                else
-                                {
-                                    location = f.getName();
-                                    if (!getName().isEmpty())
-                                    {
-                                        location = getName() + ":" + location;
-                                    }
-                                }
-                                Version version = Version.parseVersion(
-                                    headers.getValue(Constants.BUNDLE_VERSION));
-                                AtomosContentBase bundle = new AtomosContentSubstrate(
-                                    location, symbolicName, version, connectContent);
-                                bundles.add(bundle);
-                            }
-                        }
-                        catch (IOException e)
-                        {
-                            // ignore and continue
-                        }
-                    }
-                }
-            }
-        }
-
-        /**
-         * Atomos content discovered in a substrate image.  The key is this content itself
-         * which is used to lookup the content based on package name.
-         */
-        public class AtomosContentSubstrate extends AtomosContentBase
-        {
-
-            public AtomosContentSubstrate(String location, String symbolicName, Version version, ConnectContent content)
-            {
-                super(location, symbolicName, version, content);
-            }
-
-            @Override
-            protected final Object getKey()
-            {
-                // TODO a bit hokey to use ourselves as a key
-                return this;
-            }
-        }
-
-        @Override
-        public void bundleChanged(BundleEvent event)
-        {
-            if (event.getType() == BundleEvent.INSTALLED)
-            {
-                addPackages(event.getBundle());
-            }
-        }
-
-        void addPackages(Bundle b)
-        {
-            AtomosContentBase atomosContent = (AtomosContentBase) getConnectedContent(
-                b.getLocation());
-            if (atomosContent != null)
-            {
-                BundleRevision r = b.adapt(BundleRevision.class);
-                r.getDeclaredCapabilities(PackageNamespace.PACKAGE_NAMESPACE).forEach(
-                    (p) -> packageToAtomosContent.putIfAbsent((String) p.getAttributes().get(
-                        PackageNamespace.PACKAGE_NAMESPACE), atomosContent));
-                String privatePackages = b.getHeaders("").get("Private-Package");
-                if (privatePackages != null)
-                {
-                    for (String pkgName : privatePackages.split(","))
-                    {
-                        pkgName = pkgName.trim();
-                        packageToAtomosContent.put(pkgName, atomosContent);
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public AtomosLayer getBootLayer()
-    {
-        return bootLayer;
-    }
-
-    @Override
-    protected void addingLayer(AtomosLayerBase atomosLayer)
-    {
-        // nothing to do
-    }
-
-    @Override
-    protected void removedLayer(AtomosLayerBase atomosLayer)
-    {
-        // nothing to do
-    }
-
-    @Override
-    protected Object getAtomosKey(Class<?> classFromBundle)
-    {
-        return bootLayer.getContentByPackage(classFromBundle);
-    }
-
-    @Override
-    protected void start(BundleContext bc) throws BundleException
-    {
-        bc.addBundleListener(bootLayer);
-        for (Bundle b : bc.getBundles())
-        {
-            bootLayer.addPackages(b);
-        }
-        super.start(bc);
-    }
-
-    @Override
-    protected void stop(BundleContext bc) throws BundleException
-    {
-        super.stop(bc);
-        bc.removeBundleListener(bootLayer);
-    }
-
-    File getSubstrateLibDir()
-    {
-        return substrateLibDir;
-    }
-}
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/SubstrateJarConnectContent.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/SubstrateJarConnectContent.java
deleted file mode 100644
index f4b8f9e..0000000
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/impl/runtime/substrate/SubstrateJarConnectContent.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.felix.atomos.impl.runtime.substrate;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Optional;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import org.osgi.framework.connect.ConnectContent;
-
-public class SubstrateJarConnectContent implements ConnectContent
-{
-    private final AtomosRuntimeSubstrate runtime;
-    private final String fileName;
-    private volatile ZipFile zipFile;
-
-    public SubstrateJarConnectContent(String fileName, AtomosRuntimeSubstrate runtime)
-    {
-        this.fileName = fileName;
-        this.runtime = runtime;
-    }
-
-    @Override
-    public void open() throws IOException
-    {
-        ZipFile current = zipFile;
-        if (current == null)
-        {
-            zipFile = new ZipFile(new File(runtime.getSubstrateLibDir(), fileName));
-        }
-    }
-
-    @Override
-    public void close() throws IOException
-    {
-        ZipFile current = zipFile;
-        if (current != null)
-        {
-            zipFile = null;
-            current.close();
-        }
-    }
-
-    @Override
-    public Optional<ClassLoader> getClassLoader()
-    {
-        return Optional.of(getClass().getClassLoader());
-    }
-
-    @Override
-    public Iterable<String> getEntries() throws IOException
-    {
-        return () -> new Iterator<String>()
-        {
-            final Enumeration<? extends ZipEntry> entries = zipFile.entries();
-
-            @Override
-            public boolean hasNext()
-            {
-                return entries.hasMoreElements();
-            }
-
-            @Override
-            public String next()
-            {
-                return entries.nextElement().getName();
-            }
-        };
-    }
-
-    @Override
-    public Optional<ConnectEntry> getEntry(String name)
-    {
-        ZipEntry entry = zipFile.getEntry(name);
-        if (entry != null)
-        {
-            return Optional.of(new JarConnectEntry(entry));
-        }
-        return Optional.empty();
-    }
-
-    @Override
-    public Optional<Map<String, String>> getHeaders()
-    {
-        return Optional.empty();
-    }
-
-    class JarConnectEntry implements ConnectEntry
-    {
-        final ZipEntry entry;
-
-        public JarConnectEntry(ZipEntry entry)
-        {
-            this.entry = entry;
-        }
-
-        @Override
-        public long getContentLength()
-        {
-            return entry.getSize();
-        }
-
-        @Override
-        public InputStream getInputStream() throws IOException
-        {
-            return zipFile.getInputStream(entry);
-        }
-
-        @Override
-        public long getLastModified()
-        {
-            return entry.getTime();
-        }
-
-        @Override
-        public String getName()
-        {
-            return entry.getName();
-        }
-
-    }
-}
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/launch/AtomosLauncher.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/launch/AtomosLauncher.java
index deed6c4..6862ed3 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/launch/AtomosLauncher.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/launch/AtomosLauncher.java
@@ -77,7 +77,7 @@ public class AtomosLauncher
     public static Framework launch(Map<String, String> frameworkConfig)
         throws BundleException
     {
-        AtomosRuntime atomosRuntime = AtomosRuntime.newAtomosRuntime();
+        AtomosRuntime atomosRuntime = AtomosRuntime.newAtomosRuntime(frameworkConfig);
         if (atomosRuntime.getBootLayer().isAddLayerSupported())
         {
             String modulesDirPath = frameworkConfig.get(ATOMOS_MODULES_DIR);
diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/runtime/AtomosRuntime.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/runtime/AtomosRuntime.java
index bdfe4ad..566c353 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/runtime/AtomosRuntime.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/runtime/AtomosRuntime.java
@@ -14,6 +14,7 @@
 package org.apache.felix.atomos.runtime;
 
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.Map;
 
 import org.apache.felix.atomos.impl.runtime.base.AtomosRuntimeBase;
@@ -142,13 +143,14 @@ public interface AtomosRuntime
      * will not be automatically installed as bundles. Default is true, which
      * will install all discovered Atomos content as bundles.
      */
-    String ATOMOS_CONTENT_INSTALL = "atomos.content.install";
+    String ATOMOS_CONTENT_INSTALL = AtomosRuntimeBase.ATOMOS_PROP_PREFIX
+        + "content.install";
     /**
      * Framework launching property specifying if the Atomos contents installed
      * as connected bundles will not be marked for start. Default is true, which
      * will start all discovered Atomos content that are installed as bundles.
      */
-    String ATOMOS_CONTENT_START = "atomos.content.start";
+    String ATOMOS_CONTENT_START = AtomosRuntimeBase.ATOMOS_PROP_PREFIX + "content.start";
 
     /**
      * Returns the Atomos content that is connected with the specified bundle location.
@@ -183,6 +185,18 @@ public interface AtomosRuntime
 
     /**
      * Creates a new AtomosRuntime that can be used to create a new OSGi framework
+     * instance. Same as calling {@code newAtomosRuntime(Map)} with a {@code null} 
+     * configuration.
+     * 
+     * @return a new AtomosRuntime.
+     */
+    static AtomosRuntime newAtomosRuntime()
+    {
+        return newAtomosRuntime(Collections.emptyMap());
+    }
+
+    /**
+     * Creates a new AtomosRuntime that can be used to create a new OSGi framework
      * instance. If Atomos is running as a Java Module then this AtomosRuntime can
      * be used to create additional layers by using the
      * {@link AtomosLayer#addLayer(String, AtomosLayer.LoaderType, Path...)} method. If the additional layers are added
@@ -195,10 +209,11 @@ public interface AtomosRuntime
      * {@link ConnectFrameworkFactory#newFramework(Map, ModuleConnector)} instance to use
      * the layers added to this {@code AtomosRuntime}.
      * 
+     * @param configuration the properties to configure the new runtime
      * @return a new AtomosRuntime.
      */
-    static AtomosRuntime newAtomosRuntime()
+    static AtomosRuntime newAtomosRuntime(Map<String, String> configuration)
     {
-        return AtomosRuntimeBase.newAtomosRuntime();
+        return AtomosRuntimeBase.newAtomosRuntime(configuration);
     }
 }
diff --git a/atomos.tests/atomos.tests.index.bundles/pom.xml b/atomos.tests/atomos.tests.index.bundles/pom.xml
new file mode 100644
index 0000000..e327c0a
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.felix.atomos.tests</groupId>
+        <artifactId>org.apache.felix.atomos.tests</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <artifactId>org.apache.felix.atomos.tests.index.bundles</artifactId>
+    <name>atomos.tests.index.bundles</name>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.atomos.runtime</artifactId>
+            <version>${atomos.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix.atomos.tests</groupId>
+            <artifactId>org.apache.felix.atomos.tests.testbundles.service.impl</artifactId>
+            <version>${atomos.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunch.java b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunch.java
new file mode 100644
index 0000000..43bd322
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunch.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.atomos.tests.index.bundles;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.atomos.impl.runtime.base.AtomosRuntimeBase;
+import org.apache.felix.atomos.launch.AtomosLauncher;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+public class IndexLaunch
+{
+    private static volatile Framework framework;
+
+    public static void main(String[] args) throws BundleException
+    {
+        long start = System.nanoTime();
+        Map<String, String> config = AtomosLauncher.getConfiguration(args);
+        config.put(AtomosRuntimeBase.ATOMOS_LOAD_INDEX_PROP,
+            AtomosRuntimeBase.Index.FIRST.toString());
+        framework = AtomosLauncher.launch(config);
+        long total = System.nanoTime() - start;
+        System.out.println("Total time: " + TimeUnit.NANOSECONDS.toMillis(total));
+    }
+
+    public static Framework getFramework()
+    {
+        return framework;
+    }
+}
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b1/ActivatorBundle1.java b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b1/ActivatorBundle1.java
new file mode 100644
index 0000000..619f84a
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b1/ActivatorBundle1.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.atomos.tests.index.bundles.b1;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+public class ActivatorBundle1 implements BundleActivator
+{
+    @Override
+    public void start(BundleContext context) throws Exception
+    {
+        context.registerService(BundleActivator.class, this,
+            new Hashtable<>(Map.of("test.activator", this.getClass().getSimpleName(),
+                "test.bundle", FrameworkUtil.getBundle(this.getClass()))));
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception
+    {
+    }
+}
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b2/ActivatorBundle2.java b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b2/ActivatorBundle2.java
new file mode 100644
index 0000000..b972403
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b2/ActivatorBundle2.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.atomos.tests.index.bundles.b2;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+public class ActivatorBundle2 implements BundleActivator
+{
+    @Override
+    public void start(BundleContext context) throws Exception
+    {
+        context.registerService(BundleActivator.class, this,
+            new Hashtable<>(Map.of("test.activator", this.getClass().getSimpleName(),
+                "test.bundle", FrameworkUtil.getBundle(this.getClass()))));
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception
+    {
+    }
+}
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b3/ActivatorBundle3.java b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b3/ActivatorBundle3.java
new file mode 100644
index 0000000..653f7bf
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b3/ActivatorBundle3.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.atomos.tests.index.bundles.b3;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+public class ActivatorBundle3 implements BundleActivator
+{
+    @Override
+    public void start(BundleContext context) throws Exception
+    {
+        context.registerService(BundleActivator.class, this,
+            new Hashtable<>(Map.of("test.activator", this.getClass().getSimpleName(),
+                "test.bundle", FrameworkUtil.getBundle(this.getClass()))));
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception
+    {
+    }
+}
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b4/ActivatorBundle4.java b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b4/ActivatorBundle4.java
new file mode 100644
index 0000000..4be07ee
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/java/org/apache/felix/atomos/tests/index/bundles/b4/ActivatorBundle4.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.atomos.tests.index.bundles.b4;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+public class ActivatorBundle4 implements BundleActivator
+{
+    @Override
+    public void start(BundleContext context) throws Exception
+    {
+        context.registerService(BundleActivator.class, this,
+            new Hashtable<>(Map.of("test.activator", this.getClass().getSimpleName(),
+                "test.bundle", FrameworkUtil.getBundle(this.getClass()))));
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception
+    {
+    }
+}
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/META-INF/MANIFEST.MF b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..ab6f2e1
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/META-INF/MANIFEST.MF
@@ -0,0 +1,5 @@
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: bundle.1
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.felix.atomos.tests.index.bundles.b1.ActivatorBundle1
+
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/bundle.1-1.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/bundle.1-1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/bundle.1-1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/bundle.1-2.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/bundle.1-2.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/bundle.1-2.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/common.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/common.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/1/OSGI-INF/common.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/META-INF/MANIFEST.MF b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..da32c29
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/META-INF/MANIFEST.MF
@@ -0,0 +1,5 @@
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: bundle.2
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.felix.atomos.tests.index.bundles.b2.ActivatorBundle2
+
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/bundle.2-1.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/bundle.2-1.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/bundle.2-1.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/bundle.2-2.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/bundle.2-2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/bundle.2-2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/common.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/common.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/2/OSGI-INF/common.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/META-INF/MANIFEST.MF b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..be4e3d3
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/META-INF/MANIFEST.MF
@@ -0,0 +1,5 @@
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: bundle.3
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.felix.atomos.tests.index.bundles.b3.ActivatorBundle3
+
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/bundle.3-1.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/bundle.3-1.txt
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/bundle.3-1.txt
@@ -0,0 +1 @@
+3
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/bundle.3-2.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/bundle.3-2.txt
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/bundle.3-2.txt
@@ -0,0 +1 @@
+3
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/common.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/common.txt
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/3/OSGI-INF/common.txt
@@ -0,0 +1 @@
+3
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/META-INF/MANIFEST.MF b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..312a37b
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/META-INF/MANIFEST.MF
@@ -0,0 +1,5 @@
+Bundle-ManifestVersion: 2
+Bundle-SymbolicName: bundle.4
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.felix.atomos.tests.index.bundles.b4.ActivatorBundle4
+
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/bundle.4-1.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/bundle.4-1.txt
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/bundle.4-1.txt
@@ -0,0 +1 @@
+4
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/bundle.4-2.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/bundle.4-2.txt
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/bundle.4-2.txt
@@ -0,0 +1 @@
+4
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/common.txt b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/common.txt
new file mode 100644
index 0000000..bf0d87a
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/4/OSGI-INF/common.txt
@@ -0,0 +1 @@
+4
\ No newline at end of file
diff --git a/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/bundles.index b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/bundles.index
new file mode 100644
index 0000000..126c1fc
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/main/resources/atomos/bundles.index
@@ -0,0 +1,36 @@
+ATOMOS_BUNDLE
+1
+bundle.1
+1.0.0
+META-INF/MANIFEST.MF
+OSGI-INF/common.txt
+OSGI-INF/bundle.1-1.txt
+OSGI-INF/bundle.1-2.txt
+org/apache/felix/atomos/tests/index/bundles/b1/
+ATOMOS_BUNDLE
+2
+bundle.2
+1.0.0
+META-INF/MANIFEST.MF
+OSGI-INF/common.txt
+OSGI-INF/bundle.2-1.txt
+OSGI-INF/bundle.2-2.txt
+org/apache/felix/atomos/tests/index/bundles/b2/
+ATOMOS_BUNDLE
+3
+bundle.3
+1.0.0
+META-INF/MANIFEST.MF
+OSGI-INF/common.txt
+OSGI-INF/bundle.3-1.txt
+OSGI-INF/bundle.3-2.txt
+org/apache/felix/atomos/tests/index/bundles/b3/
+ATOMOS_BUNDLE
+4
+bundle.4
+1.0.0
+META-INF/MANIFEST.MF
+OSGI-INF/common.txt
+OSGI-INF/bundle.4-1.txt
+OSGI-INF/bundle.4-2.txt
+org/apache/felix/atomos/tests/index/bundles/b4/
diff --git a/atomos.tests/atomos.tests.index.bundles/src/test/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunchTest.java b/atomos.tests/atomos.tests.index.bundles/src/test/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunchTest.java
new file mode 100644
index 0000000..d00bd33
--- /dev/null
+++ b/atomos.tests/atomos.tests.index.bundles/src/test/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunchTest.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.atomos.tests.index.bundles;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Optional;
+
+import org.apache.felix.atomos.runtime.AtomosContent;
+import org.apache.felix.atomos.runtime.AtomosLayer;
+import org.apache.felix.atomos.runtime.AtomosRuntime;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.launch.Framework;
+
+public class IndexLaunchTest
+{
+
+    private static final String TESTBUNDLES_SERVICE_IMPL = "org.apache.felix.atomos.tests.testbundles.service.impl";
+    private static final String INDEX_BSN_PREFIX = "bundle.";
+
+    private Framework testFramework;
+
+    @AfterEach
+    void afterTest() throws BundleException, InterruptedException, IOException
+    {
+        if (testFramework != null && testFramework.getState() == Bundle.ACTIVE)
+        {
+            testFramework.stop();
+            testFramework.waitForStop(10000);
+        }
+    }
+
+    private AtomosRuntime getRuntime(BundleContext bc)
+    {
+        ServiceReference<AtomosRuntime> ref = bc.getServiceReference(AtomosRuntime.class);
+        assertNotNull(ref, "No reference found.");
+        AtomosRuntime runtime = bc.getService(ref);
+        assertNotNull(runtime, "No service found.");
+        return runtime;
+    }
+
+    @Test
+    void testFindBundle(@TempDir Path storage) throws BundleException
+    {
+        IndexLaunch.main(new String[] {
+                Constants.FRAMEWORK_STORAGE + '=' + storage.toFile().getAbsolutePath() });
+        testFramework = IndexLaunch.getFramework();
+        BundleContext bc = testFramework.getBundleContext();
+        assertNotNull(bc, "No context found.");
+
+        AtomosRuntime runtime = getRuntime(bc);
+        assertFindBundle("java.base", runtime.getBootLayer(), runtime.getBootLayer(),
+            true);
+        assertFindBundle(TESTBUNDLES_SERVICE_IMPL, runtime.getBootLayer(),
+            runtime.getBootLayer(), true);
+
+        for (int i = 1; i <= 4; i++)
+        {
+            assertFindBundle(INDEX_BSN_PREFIX + i, runtime.getBootLayer(),
+                runtime.getBootLayer(), true);
+        }
+        assertFindBundle("not.found", runtime.getBootLayer(), null, false);
+    }
+
+    @Test
+    void testActivatorService(@TempDir Path storage)
+        throws BundleException, InvalidSyntaxException
+    {
+        IndexLaunch.main(new String[] {
+                Constants.FRAMEWORK_STORAGE + '=' + storage.toFile().getAbsolutePath() });
+        testFramework = IndexLaunch.getFramework();
+        BundleContext bc = testFramework.getBundleContext();
+        assertNotNull(bc, "No context found.");
+
+        for (int i = 1; i <= 4; i++)
+        {
+            assertFindActivatorService(bc, i);
+        }
+    }
+
+    private void assertFindActivatorService(BundleContext bc, int i)
+        throws InvalidSyntaxException
+    {
+        Collection<ServiceReference<BundleActivator>> activatorRefs = bc.getServiceReferences(
+            BundleActivator.class, "(test.activator=ActivatorBundle" + i + ")");
+        assertEquals(1, activatorRefs.size(), "Wrong number of services for: " + i);
+        ServiceReference<BundleActivator> ref = activatorRefs.iterator().next();
+        assertEquals(ref.getBundle(), ref.getProperty("test.bundle"), "Wrong bundle.");
+    }
+
+    @Test
+    void testGetEntry(@TempDir Path storage) throws BundleException, IOException
+    {
+        IndexLaunch.main(new String[] {
+                Constants.FRAMEWORK_STORAGE + '=' + storage.toFile().getAbsolutePath() });
+        testFramework = IndexLaunch.getFramework();
+        BundleContext bc = testFramework.getBundleContext();
+        assertNotNull(bc, "No context found.");
+
+        AtomosRuntime runtime = getRuntime(bc);
+        Bundle b = assertFindBundle(TESTBUNDLES_SERVICE_IMPL,
+            runtime.getBootLayer(),
+            runtime.getBootLayer(), true).getBundle();
+        assertNotNull(b, "No bundle found.");
+        URL mf = b.getEntry("/META-INF/MANIFEST.MF");
+        assertNotNull(mf, "No manifest found.");
+
+        for (int i = 1; i <= 4; i++)
+        {
+            AtomosContent content = runtime.getBootLayer().findAtomosContent(
+                "bundle." + i).get();
+            Bundle bundle = content.getBundle();
+            URL commonURL = bundle.getEntry("OSGI-INF/common.txt");
+            assertNotNull(commonURL, "No common url found: " + i);
+            assertContent(Integer.toString(i), commonURL);
+            URL bundleResource1 = bundle.getEntry("OSGI-INF/bundle." + i + "-1.txt");
+            assertNotNull(bundleResource1, "No bundle resource 1 found: " + i);
+            assertContent(Integer.toString(i), bundleResource1);
+            URL bundleResource2 = bundle.getEntry("OSGI-INF/bundle." + i + "-2.txt");
+            assertNotNull(bundleResource2, "No bundle resource 2 found: " + i);
+            assertContent(Integer.toString(i), bundleResource2);
+        }
+    }
+
+    private void assertContent(String expected, URL url) throws IOException
+    {
+        try (BufferedReader br = new BufferedReader(
+            new InputStreamReader(url.openStream())))
+        {
+            assertEquals(expected, br.readLine(), "Wrong content for: " + url);
+        }
+    }
+
+    private AtomosContent assertFindBundle(String name, AtomosLayer layer,
+        AtomosLayer expectedLayer, boolean expectedToFind)
+    {
+        Optional<AtomosContent> result = layer.findAtomosContent(name);
+        if (expectedToFind)
+        {
+            assertTrue(result.isPresent(), "Could not find bundle: " + name);
+            assertEquals(name, result.get().getSymbolicName(), "Wrong name");
+            assertEquals(expectedLayer, result.get().getAtomosLayer(),
+                "Wrong layer for bundle: " + name);
+            Bundle b = result.get().getBundle();
+            assertNotNull(b, "No Bundle.");
+            assertEquals(name, b.getSymbolicName(), "Wrong BSN");
+        }
+        else
+        {
+            assertFalse(result.isPresent(), "Found unexpected bundle: " + name);
+        }
+        return result.orElse(null);
+    }
+}
diff --git a/atomos.tests/pom.xml b/atomos.tests/pom.xml
index dcf046e..6049d86 100644
--- a/atomos.tests/pom.xml
+++ b/atomos.tests/pom.xml
@@ -14,5 +14,6 @@
         <module>atomos.tests.testbundles</module>
         <module>atomos.tests.modulepath.service</module>
         <module>atomos.tests.classpath.service</module>
+        <module>atomos.tests.index.bundles</module>
     </modules>
 </project>


[felix-atomos] 02/02: Javadoc tweaks for getConnectContent

Posted by tj...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tjwatson pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-atomos.git

commit 7576cdbad01dc2871490de99cb92d14a7f4fa40e
Author: Thomas Watson <tj...@us.ibm.com>
AuthorDate: Wed Mar 4 08:06:51 2020 -0600

    Javadoc tweaks for getConnectContent
---
 .../java/org/apache/felix/atomos/runtime/AtomosContent.java | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/atomos.runtime/src/main/java/org/apache/felix/atomos/runtime/AtomosContent.java b/atomos.runtime/src/main/java/org/apache/felix/atomos/runtime/AtomosContent.java
index 2e7aa22..b33c279 100644
--- a/atomos.runtime/src/main/java/org/apache/felix/atomos/runtime/AtomosContent.java
+++ b/atomos.runtime/src/main/java/org/apache/felix/atomos/runtime/AtomosContent.java
@@ -101,10 +101,15 @@ public interface AtomosContent extends Comparable<AtomosContent>
     Bundle install(String prefix) throws BundleException;
 
     /** 
-     * Prefer {@link #getBundle() getBundle()} to this call to  avoid concurrency issues if there is any possibility 
-     * that an OSGI framework is active and managing the associated content. If the ConnectContent is not managed by
-     * a framework, {@link #getBundle() getBundle()} will return null and this method can be called as a way to access
-     * the associated content. The caller is responsible for opening  and closing the ConnectContent as appropriate.
+     * Returns the connect content for this Atomos content. The returned {@link ConnectContent} can
+     * be used to lookup entries from the content directly. If possible, it is preferred to used the bundle
+     * returned by {@link #getBundle() getBundle()} instead to access entries from the content. Using the
+     * bundle avoids issues with accessing the content
+     * at the same time the OSGi Framework is managing the associated content.
+     * <p>
+     * If the ConnectContent is not managed by
+     * a framework, {@link #getBundle() getBundle()} will return {@code null} and this method can be called as a way to access
+     * the associated content. The caller is responsible for opening and closing the ConnectContent as appropriate.
      * 
      * @return ConnectContent associated with this Atomos content. 
      */