You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pa...@apache.org on 2021/02/12 22:57:33 UTC

[felix-atomos] branch manifestProvider created (now d9ea3e2)

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

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


      at d9ea3e2  Add a headers provider hook

This branch includes the following new commits:

     new d9ea3e2  Add a headers provider hook

The 1 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.



[felix-atomos] 01/01: Add a headers provider hook

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

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

commit d9ea3e296d024f6311c6b3ed5ddbdce1e4efbec4
Author: Karl Pauls <ka...@gmail.com>
AuthorDate: Fri Feb 12 23:56:25 2021 +0100

    Add a headers provider hook
---
 .../main/java/org/apache/felix/atomos/Atomos.java  |  46 +++-
 .../apache/felix/atomos/impl/base/AtomosBase.java  | 184 ++++++++-----
 .../felix/atomos/impl/base/AtomosClassPath.java    |   6 +-
 .../impl/content/ConnectContentCloseableJar.java   |   6 +-
 .../atomos/impl/content/ConnectContentFile.java    |   8 +-
 .../atomos/impl/content/ConnectContentJar.java     |   6 +-
 .../felix/atomos/impl/modules/AtomosModules.java   | 290 ++++++++++++++++-----
 .../atomos/impl/modules/ConnectContentModule.java  | 191 +-------------
 8 files changed, 401 insertions(+), 336 deletions(-)

diff --git a/atomos/src/main/java/org/apache/felix/atomos/Atomos.java b/atomos/src/main/java/org/apache/felix/atomos/Atomos.java
index edd2000..3ea4e42 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/Atomos.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/Atomos.java
@@ -19,6 +19,8 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
+import java.util.Optional;
+import java.util.function.BiFunction;
 
 import org.apache.felix.atomos.AtomosLayer.LoaderType;
 import org.apache.felix.atomos.impl.base.AtomosBase;
@@ -205,7 +207,7 @@ public interface Atomos
      * @param frameworkConfig The framework configuration options, or {@code null} if the defaults should be used
      * @return The new uninitialized Framework instance which uses this Atomos instance
      */
-    public Framework newFramework(Map<String, String> frameworkConfig);
+    Framework newFramework(Map<String, String> frameworkConfig);
 
     /**
      * A main method that can be used by executable jars to initialize and start
@@ -236,7 +238,7 @@ public interface Atomos
      *             cannot contain an '=' (equals) character.
      * @return a map of the configuration specified by the args
      */
-    public static Map<String, String> getConfiguration(String... args)
+    static Map<String, String> getConfiguration(String... args)
     {
         Map<String, String> config = new HashMap<>();
         if (args != null)
@@ -262,9 +264,21 @@ public interface Atomos
      * 
      * @return a new Atomos.
      */
-    static Atomos newAtomos()
+     static Atomos newAtomos()
     {
-        return newAtomos(Collections.emptyMap());
+        return newAtomos((location, headers) -> Optional.empty());
+    }
+
+    /**
+     * Creates a new Atomos that can be used to create a new OSGi framework
+     * instance. Same as calling {@code newAtomos(Map)} with an empty
+     * configuration.
+     *
+     * @return a new Atomos.
+     */
+    static Atomos newAtomos(BiFunction<String, Map<String, String>, Optional<Map<String, String>>> headerProvider)
+    {
+        return newAtomos(Collections.emptyMap(), headerProvider);
     }
 
     /**
@@ -286,6 +300,28 @@ public interface Atomos
      */
     static Atomos newAtomos(Map<String, String> configuration)
     {
-        return AtomosBase.newAtomos(configuration);
+        return newAtomos(configuration, (location, headers) -> Optional.empty());
+    }
+
+    /**
+     * Creates a new Atomos that can be used to create a new OSGi framework
+     * instance. If Atomos is running as a Java Module then this Atomos can
+     * be used to create additional layers by using the
+     * {@link AtomosLayer#addLayer(String, AtomosLayer.LoaderType, Path...)} method. If the additional layers are added
+     * before {@link ConnectFrameworkFactory#newFramework(Map, ModuleConnector)}  creating} and {@link Framework#init()
+     * initializing} the framework then the Atomos contents found in the added layers
+     * will be automatically installed and started according to the
+     * {@link #ATOMOS_CONTENT_INSTALL} and {@link #ATOMOS_CONTENT_START} options.
+     * <p>
+     * Note that this {@code Atomos} must be used for creating a new
+     * {@link ConnectFrameworkFactory#newFramework(Map, ModuleConnector)} instance to use
+     * the layers added to this {@code Atomos}.
+     *
+     * @param configuration the properties to configure the new runtime
+     * @return a new Atomos.
+     */
+    static Atomos newAtomos(Map<String, String> configuration, BiFunction<String, Map<String, String>, Optional<Map<String, String>>> headerProvider)
+    {
+        return AtomosBase.newAtomos(configuration, headerProvider);
     }
 }
diff --git a/atomos/src/main/java/org/apache/felix/atomos/impl/base/AtomosBase.java b/atomos/src/main/java/org/apache/felix/atomos/impl/base/AtomosBase.java
index 9ee0aa2..751c9b4 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/impl/base/AtomosBase.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/impl/base/AtomosBase.java
@@ -18,6 +18,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.UncheckedIOException;
+import java.lang.reflect.InvocationTargetException;
 import java.net.JarURLConnection;
 import java.net.URISyntaxException;
 import java.net.URL;
@@ -42,6 +43,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.BiFunction;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
 import java.util.jar.Manifest;
@@ -132,47 +134,57 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
 
     protected final AtomicLong nextLayerId = new AtomicLong(0);
 
+    protected final BiFunction<String, Map<String, String>, Optional<Map<String, String>>> headerProvider;
+
     public static enum Index
     {
         IGNORE, FIRST
     }
 
-    public static Atomos newAtomos(Map<String, String> config)
+    public static Atomos newAtomos(Map<String, String> config, BiFunction<String, Map<String, String>, Optional<Map<String, String>>> headerProvider)
     {
         String runtimeClass = config.get(ATOMOS_CLASS_PROP);
         if (runtimeClass != null)
         {
-            return loadRuntime(runtimeClass, config);
+            return loadRuntime(runtimeClass, config, headerProvider);
         }
         if (config.get(
             ATOMOS_LIB_DIR_PROP) != null
             || System.getProperty(GRAAL_NATIVE_IMAGE_KIND) != null)
         {
-            return new AtomosClassPath(config);
+            return new AtomosClassPath(config, headerProvider);
         }
         try
         {
             Class.forName("java.lang.Module");
-            return loadRuntime(ATOMOS_RUNTIME_MODULES_CLASS, config);
+            return loadRuntime(ATOMOS_RUNTIME_MODULES_CLASS, config, headerProvider);
         }
         catch (ClassNotFoundException e)
         {
             // ignore
         }
         // default to classpath
-        return new AtomosClassPath(config);
+        return new AtomosClassPath(config, headerProvider);
     }
 
     private static Atomos loadRuntime(String runtimeClass,
-        Map<String, String> config)
+        Map<String, String> config, BiFunction<String, Map<String, String>, Optional<Map<String, String>>> manifestProvider)
     {
         try
         {
             return (AtomosBase) Class.forName(
-                runtimeClass).getConstructor(Map.class).newInstance(config);
+                runtimeClass).getConstructor(Map.class, BiFunction.class).newInstance(config, manifestProvider);
         }
         catch (Exception e)
         {
+            if (e instanceof InvocationTargetException)
+            {
+                Throwable cause = e.getCause();
+                if (cause instanceof Exception)
+                {
+                    e = (Exception) cause;
+                }
+            }
             throw e instanceof RuntimeException ? (RuntimeException) e
                 : new RuntimeException(e);
         }
@@ -184,9 +196,10 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
         return new File(libDirProp, ATOMOS_LIB_DIR);
     }
 
-    protected AtomosBase(Map<String, String> config)
+    protected AtomosBase(Map<String, String> config, BiFunction<String, Map<String, String>, Optional<Map<String, String>>> headerProvider)
     {
         saveConfig(config);
+        this.headerProvider = headerProvider;
         DEBUG = Boolean.parseBoolean(this.config.get(ATOMOS_DEBUG_PROP));
         REPORT_RESOLUTION_ERRORS = Boolean.parseBoolean(
             this.config.get(ATOMOS_REPORT_RESOLUTION_PROP));
@@ -734,64 +747,70 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
                     JarFile.MANIFEST_NAME);
                 while (classpathManifests.hasMoreElements())
                 {
-                    URL manifest = classpathManifests.nextElement();
-                    if (parentManifests.contains(manifest))
+                    URL manifestURL = classpathManifests.nextElement();
+                    if (parentManifests.contains(manifestURL))
                     {
                         // ignore parent manifests
                         continue;
                     }
-                    Attributes headers = new Manifest(
-                        manifest.openStream()).getMainAttributes();
-                    String symbolicName = headers.getValue(Constants.BUNDLE_SYMBOLICNAME);
-                    if (symbolicName != null)
+
+                    Object content = getBundleContent(manifestURL);
+                    if (content != null)
                     {
-                        int semiColon = symbolicName.indexOf(';');
-                        if (semiColon != -1)
+                        ManifestHolder holder = new ManifestHolder();
+
+                        ConnectContent connectContent;
+                        URL url;
+                        if (content instanceof File)
                         {
-                            symbolicName = symbolicName.substring(0, semiColon);
+                            connectContent = new ConnectContentFile((File) content, holder::getHeaders);
+                            url = ((File) content).toURI().toURL();
                         }
-                        symbolicName = symbolicName.trim();
-
-                        Object content = getBundleContent(manifest);
-                        if (content != null)
+                        else
                         {
-                            ConnectContent connectContent;
-                            URL url;
-                            if (content instanceof File)
-                            {
-                                connectContent = new ConnectContentFile((File) content);
-                                url = ((File) content).toURI().toURL();
-
-                            }
-                            else
-                            {
-                                connectContent = new ConnectContentJar(
-                                    () -> ((JarFile) content), null);
-                                url = new File(
+                            connectContent = new ConnectContentJar(
+                                    () -> ((JarFile) content), null, holder::getHeaders);
+                            url = new File(
                                     ((JarFile) content).getName()).toURI().toURL();
-                            }
+                        }
 
-                            String location;
-                            if (connectContent.getEntry(
+                        String location;
+                        if (connectContent.getEntry(
                                 "META-INF/services/org.osgi.framework.launch.FrameworkFactory").isPresent())
+                        {
+                            location = Constants.SYSTEM_BUNDLE_LOCATION;
+                        }
+                        else
+                        {
+                            location = content instanceof File
+                                    ? ((File) content).getPath()
+                                    : ((JarFile) content).getName();
+                            if (!getName().isEmpty())
                             {
-                                location = Constants.SYSTEM_BUNDLE_LOCATION;
+                                location = getName() + ":" + location;
                             }
-                            else
+                        }
+
+                        Map<String, String> headers = toMap(new Manifest(manifestURL.openStream()));
+
+                        headers = headerProvider.apply(location, headers).orElse(headers);
+
+                        holder.setHeaders(Optional.of(headers));
+
+                        String symbolicName = headers.get(Constants.BUNDLE_SYMBOLICNAME);
+                        if (symbolicName != null)
+                        {
+                            int semiColon = symbolicName.indexOf(';');
+                            if (semiColon != -1)
                             {
-                                location = content instanceof File
-                                    ? ((File) content).getPath()
-                                    : ((JarFile) content).getName();
-                                if (!getName().isEmpty())
-                                {
-                                    location = getName() + ":" + location;
-                                }
+                                symbolicName = symbolicName.substring(0, semiColon);
                             }
-                            Version version = Version.parseVersion(
-                                headers.getValue(Constants.BUNDLE_VERSION));
+                            symbolicName = symbolicName.trim();
+
+                            Version version = Version.parseVersion(headers.get(Constants.BUNDLE_VERSION));
 
                             result.add(new AtomosContentClassPath(location, symbolicName,
-                                version, connectContent, url));
+                                    version, connectContent, url));
                         }
                     }
                 }
@@ -831,20 +850,10 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
                 {
                     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();
+                            ManifestHolder holder = new ManifestHolder();
 
                             ConnectContent connectContent = new ConnectContentCloseableJar(
-                                f.getName(), () -> atomosLibDir);
+                                f.getName(), () -> atomosLibDir, holder::getHeaders);
                             connectContent.open();
                             String location;
                             if (connectContent.getEntry(
@@ -860,12 +869,29 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
                                     location = getName() + ":" + location;
                                 }
                             }
-                            Version version = Version.parseVersion(
-                                headers.getValue(Constants.BUNDLE_VERSION));
-                            AtomosContentBase bundle = new AtomosContentIndexed(location,
-                                symbolicName, version, connectContent);
-                            bootBundles.add(bundle);
-                        }
+                            Map<String, String> headers = toMap(jar.getManifest());
+
+                            headers = headerProvider.apply(location, headers).orElse(headers);
+
+                            holder.setHeaders(Optional.of(headers));
+
+                            String symbolicName = headers.get(
+                                    Constants.BUNDLE_SYMBOLICNAME);
+                            if (symbolicName != null)
+                            {
+                                int semiColon = symbolicName.indexOf(';');
+                                if (semiColon != -1)
+                                {
+                                    symbolicName = symbolicName.substring(0, semiColon);
+                                }
+                                symbolicName = symbolicName.trim();
+
+                                Version version = Version.parseVersion(
+                                    headers.get(Constants.BUNDLE_VERSION));
+                                AtomosContentBase bundle = new AtomosContentIndexed(location,
+                                    symbolicName, version, connectContent);
+                                bootBundles.add(bundle);
+                            }
                     }
                     catch (IOException e)
                     {
@@ -1395,6 +1421,30 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
                 return this;
             }
         }
+
+        public final class ManifestHolder {
+            private volatile Optional<Map<String, String>> headers = Optional.empty();
+
+            public void setHeaders(Optional<Map<String, String>> headers) {
+                this.headers = headers;
+            }
+
+            public Optional<Map<String, String>> getHeaders() {
+                return headers;
+            }
+        }
+    }
+
+    protected Map<String, String> toMap(Manifest manifest)
+    {
+        Map<String, String> result = new HashMap<>();
+        Attributes attributes = manifest.getMainAttributes();
+        for (Object key : attributes.keySet())
+        {
+            String keyString = key.toString();
+            result.put(keyString, manifest.getMainAttributes().getValue(keyString));
+        }
+        return result;
     }
 
     @Override
diff --git a/atomos/src/main/java/org/apache/felix/atomos/impl/base/AtomosClassPath.java b/atomos/src/main/java/org/apache/felix/atomos/impl/base/AtomosClassPath.java
index fdf279c..0150b19 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/impl/base/AtomosClassPath.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/impl/base/AtomosClassPath.java
@@ -22,6 +22,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.Optional;
 
 import org.apache.felix.atomos.AtomosContent;
 import org.apache.felix.atomos.AtomosLayer;
@@ -34,9 +36,9 @@ public class AtomosClassPath extends AtomosBase
 
     private final AtomosLayer bootLayer = createBootLayer();
 
-    public AtomosClassPath(Map<String, String> config)
+    public AtomosClassPath(Map<String, String> config, BiFunction<String, Map<String, String>, Optional<Map<String, String>>> headerProvider)
     {
-        super(config);
+        super(config, headerProvider);
     }
 
     private AtomosLayer createBootLayer()
diff --git a/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentCloseableJar.java b/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentCloseableJar.java
index e02bd9c..96f2b3b 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentCloseableJar.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentCloseableJar.java
@@ -17,6 +17,8 @@ import static org.apache.felix.atomos.impl.base.AtomosBase.sneakyThrow;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Map;
+import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.zip.ZipFile;
@@ -81,9 +83,9 @@ public class ConnectContentCloseableJar extends ConnectContentJar
 
     }
 
-    public ConnectContentCloseableJar(String fileName, Supplier<File> rootSupplier)
+    public ConnectContentCloseableJar(String fileName, Supplier<File> rootSupplier, Supplier<Optional<Map<String, String>>> headers)
     {
         super(new ZipFileHolder(fileName, rootSupplier),
-            z -> ((ZipFileHolder) z).accept(z));
+            z -> ((ZipFileHolder) z).accept(z), headers);
     }
 }
diff --git a/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentFile.java b/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentFile.java
index 7069585..5194505 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentFile.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentFile.java
@@ -21,6 +21,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 import org.osgi.framework.connect.ConnectContent;
@@ -77,9 +78,12 @@ public class ConnectContentFile implements ConnectContent
 
     final File root;
 
-    public ConnectContentFile(File root)
+    final Supplier<Optional<Map<String, String>>> headers;
+
+    public ConnectContentFile(File root, Supplier<Optional<Map<String, String>>> headers)
     {
         this.root = root;
+        this.headers = headers;
     }
 
     @Override
@@ -152,7 +156,7 @@ public class ConnectContentFile implements ConnectContent
     @Override
     public Optional<Map<String, String>> getHeaders()
     {
-        return Optional.empty();
+        return headers.get();
     }
 
 }
diff --git a/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentJar.java b/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentJar.java
index 1875a1c..2d323fd 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentJar.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentJar.java
@@ -30,11 +30,13 @@ public class ConnectContentJar implements ConnectContent
 {
     final Supplier<ZipFile> zipSupplier;
     final Consumer<Supplier<ZipFile>> closer;
+    final Supplier<Optional<Map<String, String>>> headers;
 
-    public ConnectContentJar(Supplier<ZipFile> zipSupplier, Consumer<Supplier<ZipFile>> closer)
+    public ConnectContentJar(Supplier<ZipFile> zipSupplier, Consumer<Supplier<ZipFile>> closer, Supplier<Optional<Map<String, String>>> headers)
     {
         this.zipSupplier = zipSupplier;
         this.closer = closer;
+        this.headers = headers;
     }
 
     @Override
@@ -93,7 +95,7 @@ public class ConnectContentJar implements ConnectContent
     @Override
     public Optional<Map<String, String>> getHeaders()
     {
-        return Optional.empty();
+        return headers.get();
     }
 
     class JarConnectEntry implements ConnectEntry
diff --git a/atomos/src/main/java/org/apache/felix/atomos/impl/modules/AtomosModules.java b/atomos/src/main/java/org/apache/felix/atomos/impl/modules/AtomosModules.java
index dc79eb9..0facc64 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/impl/modules/AtomosModules.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/impl/modules/AtomosModules.java
@@ -38,8 +38,10 @@ import java.util.Optional;
 import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.jar.Attributes;
+import java.util.jar.JarFile;
 import java.util.jar.Manifest;
 import java.util.stream.Collectors;
 
@@ -48,13 +50,17 @@ import org.apache.felix.atomos.AtomosLayer;
 import org.apache.felix.atomos.Atomos;
 import org.apache.felix.atomos.AtomosLayer.LoaderType;
 import org.apache.felix.atomos.impl.base.AtomosBase;
+import org.apache.felix.atomos.impl.base.JavaServiceNamespace;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
+import org.osgi.framework.connect.ConnectContent;
 import org.osgi.framework.connect.ConnectFrameworkFactory;
 import org.osgi.framework.launch.FrameworkFactory;
+import org.osgi.framework.namespace.BundleNamespace;
 import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.resource.Namespace;
 
 public class AtomosModules extends AtomosBase
 {
@@ -64,9 +70,9 @@ public class AtomosModules extends AtomosBase
     private final Map<Configuration, AtomosLayerBase> byConfig = new HashMap<>();
     private final AtomosLayer bootLayer = createBootLayer();
 
-    public AtomosModules(Map<String, String> config)
+    public AtomosModules(Map<String, String> config, BiFunction<String, Map<String, String>, Optional<Map<String, String>>> headerProvider)
     {
-        super(config);
+        super(config, headerProvider);
     }
 
     private AtomosLayer createBootLayer()
@@ -284,67 +290,6 @@ public class AtomosModules extends AtomosBase
         return super.getAtomosKey(classFromBundle);
     }
 
-    private static Entry<String, Version> getBSNVersion(ResolvedModule m)
-    {
-        try (ModuleReader reader = m.reference().open())
-        {
-            return reader.find("META-INF/MANIFEST.MF").map(
-                (mf) -> getManifestBSNVersion(mf, m)).orElseGet(
-                    () -> new SimpleEntry<>(getBSN(m, null), getVersion(m, null)));
-        }
-        catch (IOException e)
-        {
-            return new SimpleEntry<>(getBSN(m, null), getVersion(m, null));
-        }
-    }
-
-    private static Entry<String, Version> getManifestBSNVersion(URI manifest,
-        ResolvedModule resolved)
-    {
-        try (InputStream is = manifest.toURL().openStream())
-        {
-            Attributes headers = new Manifest(is).getMainAttributes();
-            return new SimpleEntry<>(getBSN(resolved, headers),
-                getVersion(resolved, headers));
-        }
-        catch (IOException e)
-        {
-            return null;
-        }
-    }
-
-    private static String getBSN(ResolvedModule resolved, Attributes headers)
-    {
-        String bsnHeader = headers != null
-            ? headers.getValue(Constants.BUNDLE_SYMBOLICNAME)
-            : null;
-        if (bsnHeader == null)
-        {
-            return resolved.name();
-        }
-        int bsnEnd = bsnHeader.indexOf(';');
-        return bsnEnd < 0 ? bsnHeader.trim() : bsnHeader.substring(0, bsnEnd).trim();
-    }
-
-    private static Version getVersion(ResolvedModule resolved, Attributes headers)
-    {
-        String sVersion = headers != null ? headers.getValue(Constants.BUNDLE_VENDOR)
-            : null;
-        if (sVersion == null)
-        {
-            sVersion = resolved.reference().descriptor().version().map(
-                java.lang.module.ModuleDescriptor.Version::toString).orElse("0");
-        }
-        try
-        {
-            return Version.valueOf(sVersion);
-        }
-        catch (IllegalArgumentException e)
-        {
-            return Version.emptyVersion;
-        }
-    }
-
     @Override
     protected void filterBasedOnReadEdges(AtomosContent atomosContent,
         Collection<BundleCapability> candidates)
@@ -397,6 +342,44 @@ public class AtomosModules extends AtomosBase
                 a -> a.adapt(Module.class).isPresent()).collect(
                     Collectors.toUnmodifiableMap((k) -> k.adapt(Module.class).get(),
                         (v) -> v));
+            atomosBundles.stream().filter(bundle -> bundle instanceof AtomosContentModule && ((AtomosContentModule) bundle).requiresBSN).forEach(bundle -> {
+                bundle.getConnectContent().getHeaders().ifPresent(headers -> {
+                    // only do requires for non bundle modules
+                    // map requires to require bundle
+                    StringBuilder requireBundleHeader = new StringBuilder();
+                    for (ModuleDescriptor.Requires requires : ((AtomosContentModule) bundle).module.getDescriptor().requires())
+                    {
+                        if (requireBundleHeader.length() > 0)
+                        {
+                            requireBundleHeader.append(", ");
+                        }
+
+                        // before requiring based on the name make sure the required
+                        // module has a BSN that is the same
+                        String requiresBSN = ((AtomosContentModule) bundle).module.getLayer().findModule(requires.name()).map(
+                            m -> AtomosLayerModules.this.getAtomosContent(m).getSymbolicName()).orElse(requires.name());
+
+                        requireBundleHeader.append(requiresBSN).append("; ");
+                        // determine the resolution value based on the STATIC modifier
+                        String resolution = requires.modifiers().contains(
+                                ModuleDescriptor.Requires.Modifier.STATIC) ? Namespace.RESOLUTION_OPTIONAL
+                                : Namespace.RESOLUTION_MANDATORY;
+                        requireBundleHeader.append(Constants.RESOLUTION_DIRECTIVE).append(
+                                ":=").append(resolution).append("; ");
+                        // determine the visibility value based on the TRANSITIVE modifier
+                        String visibility = requires.modifiers().contains(
+                                ModuleDescriptor.Requires.Modifier.TRANSITIVE) ? BundleNamespace.VISIBILITY_REEXPORT
+                                : BundleNamespace.VISIBILITY_PRIVATE;
+                        requireBundleHeader.append(Constants.VISIBILITY_DIRECTIVE).append(
+                                ":=").append(visibility);
+
+                    }
+                    if (requireBundleHeader.length() > 0)
+                    {
+                        headers.put(Constants.REQUIRE_BUNDLE, requireBundleHeader.toString());
+                    }
+                });
+            });
         }
 
         @Override
@@ -450,6 +433,7 @@ public class AtomosModules extends AtomosBase
             Set<AtomosContentBase> found = new LinkedHashSet<>();
             Map<ModuleDescriptor, Module> descriptorMap = searchLayer.modules().stream().collect(
                 Collectors.toMap(Module::getDescriptor, m -> (m)));
+
             for (ResolvedModule resolved : searchLayer.configuration().modules())
             {
                 // include only if it is not excluded
@@ -483,10 +467,175 @@ public class AtomosModules extends AtomosBase
                     continue;
                 }
 
-                Entry<String, Version> bsnVersion = getBSNVersion(resolved);
-                found.add(new AtomosContentModule(resolved, m, location,
-                    bsnVersion.getKey(), bsnVersion.getValue()));
+                ManifestHolder holder = new ManifestHolder();
+
+                ConnectContent content = new ConnectContentModule(m, resolved.reference(), AtomosLayerModules.this, holder::getHeaders);
+
+                Map<String, String> headers;
+                try
+                {
+                    content.open();
+
+                    Optional<ConnectContent.ConnectEntry> entry = content.getEntry(JarFile.MANIFEST_NAME);
+
+                    if (entry.isPresent())
+                    {
+                        headers = toMap(new Manifest(entry.get().getInputStream()));
+                    }
+                    else
+                    {
+                        headers = new HashMap<>();
+                    }
+
+                    content.close();
+                }
+                catch (IOException e)
+                {
+                    throw new UncheckedIOException("Error reading connect manifest.", e);
+                }
+
+                String symbolicName = headers.get(Constants.BUNDLE_SYMBOLICNAME);
+                if (symbolicName == null)
+                {
+                    symbolicName = resolved.name();
+                }
+                int bsnEnd = symbolicName.indexOf(';');
+                symbolicName = bsnEnd < 0 ? symbolicName.trim() : symbolicName.substring(0, bsnEnd).trim();
+
+                Version version;
+
+                String sVersion = headers.get(Constants.BUNDLE_VENDOR);
+
+                if (sVersion == null)
+                {
+                    sVersion = resolved.reference().descriptor().version().map(
+                            java.lang.module.ModuleDescriptor.Version::toString).orElse("0");
+                }
+                try
+                {
+                    version = Version.valueOf(sVersion);
+                }
+                catch (IllegalArgumentException e)
+                {
+                    version = Version.emptyVersion;
+                }
+
+                ModuleDescriptor desc = m.getDescriptor();
+                StringBuilder capabilities = new StringBuilder();
+                StringBuilder requirements = new StringBuilder();
+                String bsn = headers.get(Constants.BUNDLE_SYMBOLICNAME);
+                boolean requiresBSN = false;
+                if (bsn == null)
+                {
+                    requiresBSN = true;
+                    // NOTE that we depend on the framework connect implementation to allow connect bundles
+                    // to export java.* packages
+                    headers.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+                    // set the symbolic name for the module; don't allow fragments to attach
+                    headers.put(Constants.BUNDLE_SYMBOLICNAME,
+                            symbolicName + "; " + Constants.FRAGMENT_ATTACHMENT_DIRECTIVE + ":="
+                                    + Constants.FRAGMENT_ATTACHMENT_NEVER);
+
+                    // set the version
+                    headers.put(Constants.BUNDLE_VERSION, version.toString());
+
+                    // only do exports for non bundle modules
+                    // real OSGi bundles already have good export capabilities
+                    StringBuilder exportPackageHeader = new StringBuilder();
+                    desc.exports().stream().sorted().forEach((exports) ->
+                    {
+                        if (exportPackageHeader.length() > 0)
+                        {
+                            exportPackageHeader.append(", ");
+                        }
+                        exportPackageHeader.append(exports.source());
+                        // TODO map targets to x-friends directive?
+                    });
+                    if (exportPackageHeader.length() > 0)
+                    {
+                        headers.put(Constants.EXPORT_PACKAGE, exportPackageHeader.toString());
+                    }
+                }
+                else
+                {
+                    String origCaps = headers.get(Constants.PROVIDE_CAPABILITY);
+                    if (origCaps != null)
+                    {
+                        capabilities.append(origCaps);
+                    }
+                    String origReqs = headers.get(Constants.REQUIRE_CAPABILITY);
+                    if (origReqs != null)
+                    {
+                        requirements.append(origReqs);
+                    }
+                }
+                // map provides to a made up namespace only to give proper resolution errors
+                // (although JPMS will likely complain first
+                for (ModuleDescriptor.Provides provides : desc.provides())
+                {
+                    if (capabilities.length() > 0)
+                    {
+                        capabilities.append(", ");
+                    }
+                    capabilities.append(JavaServiceNamespace.JAVA_SERVICE_NAMESPACE).append("; ");
+                    capabilities.append(JavaServiceNamespace.JAVA_SERVICE_NAMESPACE).append(
+                            "=").append(provides.service()).append("; ");
+                    capabilities.append(
+                            JavaServiceNamespace.CAPABILITY_PROVIDES_WITH_ATTRIBUTE).append(
+                            "=\"").append(String.join(",", provides.providers())).append("\"");
+                }
+
+                // map uses to a made up namespace only to give proper resolution errors
+                // (although JPMS will likely complain first)
+                for (String uses : desc.uses())
+                {
+                    if (requirements.length() > 0)
+                    {
+                        requirements.append(", ");
+                    }
+                    requirements.append(JavaServiceNamespace.JAVA_SERVICE_NAMESPACE).append("; ");
+                    requirements.append(Constants.RESOLUTION_DIRECTIVE).append(":=").append(
+                            Constants.RESOLUTION_OPTIONAL).append("; ");
+                    requirements.append(Constants.FILTER_DIRECTIVE).append(":=").append(
+                            "\"(").append(JavaServiceNamespace.JAVA_SERVICE_NAMESPACE).append(
+                            "=").append(uses).append(")\"");
+                }
+
+                if (capabilities.length() > 0)
+                {
+                    headers.put(Constants.PROVIDE_CAPABILITY, capabilities.toString());
+                }
+                if (requirements.length() > 0)
+                {
+                    headers.put(Constants.REQUIRE_CAPABILITY, requirements.toString());
+                }
 
+
+                Optional<Map<String,String>> provided = headerProvider.apply(location, headers);
+
+                if (provided.isPresent()) {
+                    requiresBSN = false;
+                }
+
+                headers = provided.orElse(headers);
+
+                holder.setHeaders(Optional.of(new HashMap<>(headers)));
+
+                symbolicName = headers.get(Constants.BUNDLE_SYMBOLICNAME);
+                if (symbolicName != null)
+                {
+                    int semiColon = symbolicName.indexOf(';');
+                    if (semiColon != -1)
+                    {
+                        symbolicName = symbolicName.substring(0, semiColon);
+                    }
+                    symbolicName = symbolicName.trim();
+
+                    version = Version.parseVersion(headers.get(Constants.BUNDLE_VERSION));
+
+                    found.add(new AtomosContentModule(m, location,
+                            symbolicName, version, content, requiresBSN));
+                }
             }
 
             return Collections.unmodifiableSet(found);
@@ -516,12 +665,13 @@ public class AtomosModules extends AtomosBase
              */
             private final Module module;
 
-            public AtomosContentModule(ResolvedModule resolvedModule, Module module, String location, String symbolicName, Version version)
+            final boolean requiresBSN;
+
+            public AtomosContentModule(Module module, String location, String symbolicName, Version version, ConnectContent content, boolean requiresBSN)
             {
-                super(location, symbolicName, version, new ConnectContentModule(module,
-                    resolvedModule.reference(), AtomosLayerModules.this, symbolicName,
-                    version));
+                super(location, symbolicName, version, content);
                 this.module = module;
+                this.requiresBSN = requiresBSN;
             }
 
             @Override
diff --git a/atomos/src/main/java/org/apache/felix/atomos/impl/modules/ConnectContentModule.java b/atomos/src/main/java/org/apache/felix/atomos/impl/modules/ConnectContentModule.java
index 4f16ff7..aafaf6c 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/impl/modules/ConnectContentModule.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/impl/modules/ConnectContentModule.java
@@ -16,45 +16,29 @@ package org.apache.felix.atomos.impl.modules;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
-import java.lang.module.ModuleDescriptor;
-import java.lang.module.ModuleDescriptor.Provides;
-import java.lang.module.ModuleDescriptor.Requires;
 import java.lang.module.ModuleReader;
 import java.lang.module.ModuleReference;
 import java.net.URI;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.jar.Attributes;
-import java.util.jar.Attributes.Name;
-import java.util.jar.Manifest;
-
-import org.apache.felix.atomos.impl.base.JavaServiceNamespace;
+import java.util.function.Supplier;
 import org.apache.felix.atomos.impl.modules.AtomosModules.AtomosLayerModules;
-import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
 import org.osgi.framework.connect.ConnectContent;
-import org.osgi.framework.namespace.BundleNamespace;
-import org.osgi.resource.Namespace;
 
 public class ConnectContentModule implements ConnectContent
 {
     final Module module;
     final ModuleReference reference;
     final AtomosLayerModules atomosLayer;
-    final String symbolicName;
-    final Version version;
-    final AtomicReference<Optional<Map<String, String>>> headers = new AtomicReference<>();
+    final Supplier<Optional<Map<String, String>>> headers;
     volatile ModuleReader reader = null;
 
-    public ConnectContentModule(Module module, ModuleReference reference, AtomosLayerModules atomosLayer, String symbolicName, Version version)
+    public ConnectContentModule(Module module, ModuleReference reference, AtomosLayerModules atomosLayer, Supplier<Optional<Map<String,String>>> headers)
     {
         this.module = module;
         this.reference = reference;
         this.atomosLayer = atomosLayer;
-        this.symbolicName = symbolicName;
-        this.version = version;
+        this.headers = headers;
     }
 
     @Override
@@ -123,174 +107,9 @@ public class ConnectContentModule implements ConnectContent
     @Override
     public Optional<Map<String, String>> getHeaders()
     {
-        return headers.updateAndGet((h) -> {
-            if (h == null)
-            {
-                h = createManifest();
-            }
-            return h;
-        });
-    }
-
-    private Optional<Map<String, String>> createManifest()
-    {
-        return Optional.of(getEntry("META-INF/MANIFEST.MF").map(
-            (mf) -> createManifest(mf)).orElseGet(() -> createManifest(null)));
-
-    }
-
-    private Map<String, String> createManifest(ConnectEntry mfEntry)
-    {
-        Map<String, String> result = new HashMap<>();
-        if (mfEntry != null)
-        {
-            try
-            {
-                Manifest mf = new Manifest(mfEntry.getInputStream());
-                Attributes mainAttrs = mf.getMainAttributes();
-                for (Object key : mainAttrs.keySet())
-                {
-                    Name name = (Name) key;
-                    result.put(name.toString(), mainAttrs.getValue(name));
-                }
-            }
-            catch (IOException e)
-            {
-                throw new UncheckedIOException("Error reading connect manfest.", e);
-            }
-        }
-
-        ModuleDescriptor desc = module.getDescriptor();
-        StringBuilder capabilities = new StringBuilder();
-        StringBuilder requirements = new StringBuilder();
-        String bsn = result.get(Constants.BUNDLE_SYMBOLICNAME);
-        if (bsn == null)
-        {
-            // NOTE that we depend on the framework connect implementation to allow connect bundles
-            // to export java.* packages
-            result.put(Constants.BUNDLE_MANIFESTVERSION, "2");
-            // set the symbolic name for the module; don't allow fragments to attach
-            result.put(Constants.BUNDLE_SYMBOLICNAME,
-                symbolicName + "; " + Constants.FRAGMENT_ATTACHMENT_DIRECTIVE + ":="
-                    + Constants.FRAGMENT_ATTACHMENT_NEVER);
-
-            // set the version
-            result.put(Constants.BUNDLE_VERSION, version.toString());
-
-            // only do exports for non bundle modules
-            // real OSGi bundles already have good export capabilities
-            StringBuilder exportPackageHeader = new StringBuilder();
-            desc.exports().stream().sorted().forEach((exports) ->
-            {
-                if (exportPackageHeader.length() > 0)
-                {
-                    exportPackageHeader.append(", ");
-                }
-                exportPackageHeader.append(exports.source());
-                // TODO map targets to x-friends directive?
-            });
-            if (exportPackageHeader.length() > 0)
-            {
-                result.put(Constants.EXPORT_PACKAGE, exportPackageHeader.toString());
-            }
-
-            // only do requires for non bundle modules
-            // map requires to require bundle
-            StringBuilder requireBundleHeader = new StringBuilder();
-            for (Requires requires : desc.requires())
-            {
-                if (requireBundleHeader.length() > 0)
-                {
-                    requireBundleHeader.append(", ");
-                }
-
-                // before requiring based on the name make sure the required
-                // module has a BSN that is the same
-                String requiresBSN = getRequiresBSN(requires.name());
-                requireBundleHeader.append(requiresBSN).append("; ");
-                // determine the resolution value based on the STATIC modifier
-                String resolution = requires.modifiers().contains(
-                    Requires.Modifier.STATIC) ? Namespace.RESOLUTION_OPTIONAL
-                        : Namespace.RESOLUTION_MANDATORY;
-                requireBundleHeader.append(Constants.RESOLUTION_DIRECTIVE).append(
-                    ":=").append(resolution).append("; ");
-                // determine the visibility value based on the TRANSITIVE modifier
-                String visibility = requires.modifiers().contains(
-                    Requires.Modifier.TRANSITIVE) ? BundleNamespace.VISIBILITY_REEXPORT
-                        : BundleNamespace.VISIBILITY_PRIVATE;
-                requireBundleHeader.append(Constants.VISIBILITY_DIRECTIVE).append(
-                    ":=").append(visibility);
-
-            }
-            if (requireBundleHeader.length() > 0)
-            {
-                result.put(Constants.REQUIRE_BUNDLE, requireBundleHeader.toString());
-            }
-        }
-        else
-        {
-            String origCaps = result.get(Constants.PROVIDE_CAPABILITY);
-            if (origCaps != null)
-            {
-                capabilities.append(origCaps);
-            }
-            String origReqs = result.get(Constants.REQUIRE_CAPABILITY);
-            if (origReqs != null)
-            {
-                requirements.append(origReqs);
-            }
-        }
-        // map provides to a made up namespace only to give proper resolution errors
-        // (although JPMS will likely complain first
-        for (Provides provides : desc.provides())
-        {
-            if (capabilities.length() > 0)
-            {
-                capabilities.append(", ");
-            }
-            capabilities.append(JavaServiceNamespace.JAVA_SERVICE_NAMESPACE).append("; ");
-            capabilities.append(JavaServiceNamespace.JAVA_SERVICE_NAMESPACE).append(
-                "=").append(provides.service()).append("; ");
-            capabilities.append(
-                JavaServiceNamespace.CAPABILITY_PROVIDES_WITH_ATTRIBUTE).append(
-                    "=\"").append(String.join(",", provides.providers())).append("\"");
-        }
-
-        // map uses to a made up namespace only to give proper resolution errors
-        // (although JPMS will likely complain first)
-        for (String uses : desc.uses())
-        {
-            if (requirements.length() > 0)
-            {
-                requirements.append(", ");
-            }
-            requirements.append(JavaServiceNamespace.JAVA_SERVICE_NAMESPACE).append("; ");
-            requirements.append(Constants.RESOLUTION_DIRECTIVE).append(":=").append(
-                Constants.RESOLUTION_OPTIONAL).append("; ");
-            requirements.append(Constants.FILTER_DIRECTIVE).append(":=").append(
-                "\"(").append(JavaServiceNamespace.JAVA_SERVICE_NAMESPACE).append(
-                    "=").append(uses).append(")\"");
-        }
-
-        if (capabilities.length() > 0)
-        {
-            result.put(Constants.PROVIDE_CAPABILITY, capabilities.toString());
-        }
-        if (requirements.length() > 0)
-        {
-            result.put(Constants.REQUIRE_CAPABILITY, requirements.toString());
-        }
-        return result;
-    }
-
-    private String getRequiresBSN(String name)
-    {
-        return module.getLayer().findModule(name).map(
-            m -> atomosLayer.getAtomosContent(m).getSymbolicName()).orElse(name);
-
+        return headers.get();
     }
 
-
     class ModuleConnectEntry implements ConnectEntry
     {
         final String name;