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;