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:34 UTC

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

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;