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/17 13:56:16 UTC
[felix-atomos] branch master updated: Add a headers provider hook
(#46)
This is an automated email from the ASF dual-hosted git repository.
pauls pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-atomos.git
The following commit(s) were added to refs/heads/master by this push:
new eb3a410 Add a headers provider hook (#46)
eb3a410 is described below
commit eb3a41073ea7c89e3f0a0d0297cd58d2c0a1cc86
Author: Karl Pauls <pa...@apache.org>
AuthorDate: Wed Feb 17 14:56:07 2021 +0100
Add a headers provider hook (#46)
* Add a new HeaderProvider that can be used to augment existing bundle manifest headers or add completely new bundle manifest headers that are not present in the existing headers.
* Allow the HeaderProvider to provide headers for content that has no headers
Co-authored-by: Thomas Watson <tj...@us.ibm.com>
---
.../service/test/ClasspathLaunchTest.java | 103 +++++++
.../tests/index/bundles/IndexLaunchTest.java | 104 +++++++
.../modulepath/service/ModulepathLaunchTest.java | 132 ++++++++-
atomos/pom.xml | 16 ++
.../main/java/org/apache/felix/atomos/Atomos.java | 100 ++++++-
.../org/apache/felix/atomos/AtomosContent.java | 2 +
.../java/org/apache/felix/atomos/AtomosLayer.java | 3 +
.../apache/felix/atomos/impl/base/AtomosBase.java | 271 +++++++++++++-----
.../felix/atomos/impl/base/AtomosClassPath.java | 4 +-
.../impl/content/ConnectContentCloseableJar.java | 6 +-
.../atomos/impl/content/ConnectContentFile.java | 8 +-
.../atomos/impl/content/ConnectContentIndexed.java | 7 +-
.../atomos/impl/content/ConnectContentJar.java | 11 +-
.../felix/atomos/impl/modules/AtomosModules.java | 309 ++++++++++++++++-----
.../atomos/impl/modules/ConnectContentModule.java | 191 +------------
15 files changed, 901 insertions(+), 366 deletions(-)
diff --git a/atomos.tests/atomos.tests.classpath.service/src/test/java/org/apache/felix/atomos/tests/classpath/service/test/ClasspathLaunchTest.java b/atomos.tests/atomos.tests.classpath.service/src/test/java/org/apache/felix/atomos/tests/classpath/service/test/ClasspathLaunchTest.java
index 9851280..bf53a12 100644
--- a/atomos.tests/atomos.tests.classpath.service/src/test/java/org/apache/felix/atomos/tests/classpath/service/test/ClasspathLaunchTest.java
+++ b/atomos.tests/atomos.tests.classpath.service/src/test/java/org/apache/felix/atomos/tests/classpath/service/test/ClasspathLaunchTest.java
@@ -26,16 +26,20 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.felix.atomos.Atomos;
+import org.apache.felix.atomos.Atomos.HeaderProvider;
import org.apache.felix.atomos.AtomosContent;
import org.apache.felix.atomos.AtomosLayer;
import org.apache.felix.atomos.AtomosLayer.LoaderType;
import org.apache.felix.atomos.impl.base.AtomosCommands;
import org.apache.felix.atomos.tests.testbundles.service.contract.Echo;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.osgi.framework.Bundle;
@@ -45,6 +49,7 @@ import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.framework.wiring.BundleCapability;
@@ -171,6 +176,104 @@ public class ClasspathLaunchTest
assertNotNull(mf, "No manifest found.");
}
+ @Test
+ void testUnmodifiableExistingHeaders(@TempDir Path storage) throws BundleException
+ {
+ AtomicBoolean fail = new AtomicBoolean(true);
+ HeaderProvider attemptModification = (location, headers) -> {
+ try
+ {
+ headers.put(Constants.BUNDLE_SYMBOLICNAME, "should.fail");
+ fail.set(true);
+ }
+ catch (UnsupportedOperationException e)
+ {
+ // expected
+ fail.set(false);
+ }
+ return Optional.empty();
+ };
+ testFramework = Atomos.newAtomos(attemptModification).newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+ if (fail.get())
+ {
+ Assertions.fail("Was able to modify the existing headers");
+ }
+ }
+
+ @Test
+ void testBundleWithCustomHeader(@TempDir Path storage) throws BundleException, InterruptedException
+ {
+ HeaderProvider headerProvider = (
+ location, headers) -> {
+ headers = new HashMap<>(headers);
+ headers.put("X-TEST", location);
+ return Optional.of(headers);
+ };
+ testFramework = Atomos.newAtomos(headerProvider).newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+ BundleContext bc = testFramework.getBundleContext();
+ assertNotNull(bc, "No context found.");
+
+ Atomos runtime = getRuntime(bc);
+ Bundle b = assertFindBundle(TESTBUNDLES_SERVICE_IMPL_A, runtime.getBootLayer(),
+ runtime.getBootLayer(), true).getBundle();
+ assertEquals(b.getLocation(), "atomos:" + b.getHeaders().get("X-TEST"));
+
+ testFramework.stop();
+ testFramework.waitForStop(10000);
+
+ // Bundles should already be installed, disable auto-install option
+ // and check the provider is still used to provide the custom header
+ // for the already installed bundle from persistence
+ testFramework = Atomos.newAtomos(Map.of(Atomos.ATOMOS_CONTENT_INSTALL, "false"),
+ headerProvider).newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+ bc = testFramework.getBundleContext();
+ assertNotNull(bc, "No context found.");
+ runtime = getRuntime(bc);
+ b = assertFindBundle(TESTBUNDLES_SERVICE_IMPL_A, runtime.getBootLayer(),
+ runtime.getBootLayer(), true).getBundle();
+ assertEquals(b.getLocation(), "atomos:" + b.getHeaders().get("X-TEST"));
+ }
+
+ @Test
+ void testHeaderProviderChangeBSN(@TempDir Path storage) throws BundleException
+ {
+ final String BSN_CONTRACT = "atomos.service.contract";
+ final String CHANGED_BSN = "changed.bsn";
+ HeaderProvider changeBSN = (location, headers) -> {
+ if (BSN_CONTRACT.equals(headers.get(Constants.BUNDLE_SYMBOLICNAME)))
+ {
+ headers = new HashMap<>(headers);
+ headers.put(Constants.BUNDLE_SYMBOLICNAME, CHANGED_BSN);
+ headers.put(Constants.BUNDLE_VERSION, "100");
+ return Optional.of(headers);
+ }
+ return Optional.empty();
+ };
+ Atomos atomos = Atomos.newAtomos(changeBSN);
+ testFramework = atomos.newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+
+ // make sure the contract names are correct
+ Bundle contractBundle = FrameworkUtil.getBundle(Echo.class);
+ assertEquals(CHANGED_BSN, contractBundle.getSymbolicName(),
+ "Wrong BSN for contract bundle.");
+ assertEquals(Version.valueOf("100"), contractBundle.getVersion());
+
+ atomos.getBootLayer().findAtomosContent(CHANGED_BSN).ifPresentOrElse((c) -> {
+ assertEquals(CHANGED_BSN, c.getSymbolicName());
+ assertEquals(Version.valueOf("100"), c.getVersion());
+ }, () -> {
+ fail("Could not find the content: " + CHANGED_BSN);
+ });
+ }
+
private AtomosContent assertFindBundle(String name, AtomosLayer layer,
AtomosLayer expectedLayer, boolean expectedToFind)
{
diff --git a/atomos.tests/atomos.tests.index.bundles/src/test/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunchTest.java b/atomos.tests/atomos.tests.index.bundles/src/test/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunchTest.java
index 1cd32bd..80fe000 100644
--- a/atomos.tests/atomos.tests.index.bundles/src/test/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunchTest.java
+++ b/atomos.tests/atomos.tests.index.bundles/src/test/java/org/apache/felix/atomos/tests/index/bundles/IndexLaunchTest.java
@@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import java.io.BufferedReader;
import java.io.IOException;
@@ -25,16 +26,21 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.felix.atomos.Atomos;
+import org.apache.felix.atomos.Atomos.HeaderProvider;
import org.apache.felix.atomos.AtomosContent;
import org.apache.felix.atomos.AtomosLayer;
import org.apache.felix.atomos.impl.base.AtomosBase;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.osgi.framework.Bundle;
@@ -44,6 +50,7 @@ import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.wiring.BundleWiring;
@@ -327,4 +334,101 @@ public class IndexLaunchTest
}
return result.orElse(null);
}
+
+ @Test
+ void testUnmodifiableExistingHeaders(@TempDir Path storage) throws BundleException
+ {
+ AtomicBoolean fail = new AtomicBoolean(true);
+ HeaderProvider attemptModification = (location, headers) -> {
+ try
+ {
+ headers.put(Constants.BUNDLE_SYMBOLICNAME, "should.fail");
+ fail.set(true);
+ }
+ catch (UnsupportedOperationException e)
+ {
+ // expected
+ fail.set(false);
+ }
+ return Optional.empty();
+ };
+ testFramework = Atomos.newAtomos(attemptModification).newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+ if (fail.get())
+ {
+ Assertions.fail("Was able to modify the existing headers");
+ }
+ }
+
+ @Test
+ void testBundleWithCustomHeader(@TempDir Path storage) throws BundleException, InterruptedException
+ {
+ HeaderProvider provider = (location, headers) -> {
+ headers = new HashMap<>(headers);
+ headers.put("X-TEST", location);
+ return Optional.of(headers);
+ };
+ testFramework = Atomos.newAtomos(provider).newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+ BundleContext bc = testFramework.getBundleContext();
+ assertNotNull(bc, "No context found.");
+
+ Consumer<AtomosContent> verifyHeader = c -> {
+ if (c.getBundle().getBundleId() == 0)
+ {
+ return;
+ }
+ String customHeader = c.getBundle().getHeaders(null).get("X-TEST");
+ assertEquals(c.getAtomosLocation(), customHeader, "Wrong header value");
+ };
+
+ Atomos atomos = getRuntime(bc);
+ atomos.getBootLayer().getAtomosContents().forEach(verifyHeader);
+
+ testFramework.stop();
+ testFramework.waitForStop(10000);
+
+ // Bundles should already be installed, disable auto-install option
+ // and check the provider is still used to provide the custom header
+ // for the already installed bundle from persistence
+ atomos = Atomos.newAtomos(Map.of(Atomos.ATOMOS_CONTENT_INSTALL, "false"),
+ provider);
+ testFramework = atomos.newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+ atomos.getBootLayer().getAtomosContents().forEach(verifyHeader);
+ }
+
+ @Test
+ void testHeaderProviderChangeBSN(@TempDir Path storage) throws BundleException
+ {
+ final String BSN_BUNDLE_1 = "bundle.1";
+ final String CHANGED_BSN = "changed.bsn";
+ HeaderProvider changeBSN = (location, headers) -> {
+ if (BSN_BUNDLE_1.equals(headers.get(Constants.BUNDLE_SYMBOLICNAME)))
+ {
+ headers = new HashMap<>(headers);
+ headers.put(Constants.BUNDLE_SYMBOLICNAME, CHANGED_BSN);
+ headers.put(Constants.BUNDLE_VERSION, "100");
+ return Optional.of(headers);
+ }
+ return Optional.empty();
+ };
+ Atomos atomos = Atomos.newAtomos(changeBSN);
+ testFramework = atomos.newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+
+ atomos.getBootLayer().findAtomosContent(CHANGED_BSN).ifPresentOrElse((c) -> {
+ assertEquals(CHANGED_BSN, c.getBundle().getSymbolicName(),
+ "Bundle symbolic name is incorrect.");
+ assertEquals(CHANGED_BSN, c.getSymbolicName(),
+ "Atomos content symbolic name is incorrect.");
+ assertEquals(Version.valueOf("100"), c.getVersion());
+ }, () -> {
+ fail("Could not find the content: " + CHANGED_BSN);
+ });
+ }
}
diff --git a/atomos.tests/atomos.tests.modulepath.service/src/test/java/org/apache/felix/atomos/tests/modulepath/service/ModulepathLaunchTest.java b/atomos.tests/atomos.tests.modulepath.service/src/test/java/org/apache/felix/atomos/tests/modulepath/service/ModulepathLaunchTest.java
index fe2dc65..0b0d31f 100644
--- a/atomos.tests/atomos.tests.modulepath.service/src/test/java/org/apache/felix/atomos/tests/modulepath/service/ModulepathLaunchTest.java
+++ b/atomos.tests/atomos.tests.modulepath.service/src/test/java/org/apache/felix/atomos/tests/modulepath/service/ModulepathLaunchTest.java
@@ -15,6 +15,7 @@ package org.apache.felix.atomos.tests.modulepath.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -30,6 +31,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -37,14 +39,17 @@ import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.apache.felix.atomos.Atomos;
+import org.apache.felix.atomos.Atomos.HeaderProvider;
import org.apache.felix.atomos.AtomosContent;
import org.apache.felix.atomos.AtomosLayer;
import org.apache.felix.atomos.AtomosLayer.LoaderType;
import org.apache.felix.atomos.tests.testbundles.service.contract.Echo;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.osgi.framework.Bundle;
@@ -55,6 +60,7 @@ import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
import org.osgi.framework.connect.ConnectFrameworkFactory;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.namespace.BundleNamespace;
@@ -249,6 +255,20 @@ public class ModulepathLaunchTest
return framework;
}
+ private Framework getFramework(Path modules, HeaderProvider provider, String... args)
+ throws BundleException
+ {
+ Map<String, String> config = Atomos.getConfiguration(args);
+ Atomos atomos = Atomos.newAtomos(config, provider);
+ if (modules != null)
+ {
+ atomos.getBootLayer().addModules("modules", modules);
+ }
+ Framework framework = atomos.newFramework(config);
+ framework.start();
+ return framework;
+ }
+
private ClassLoader getCLForResourceTests(Path storage) throws BundleException
{
Path[] testBundle = findModulePaths(TESTBUNDLES_RESOURCE_A);
@@ -897,8 +917,8 @@ public class ModulepathLaunchTest
}
}
- private static String BSN_CONTRACT = "atomos.service.contract";
- private static String BSN_SERVICE_IMPL = "org.apache.felix.atomos.tests.testbundles.service.impl";
+ private static final String BSN_CONTRACT = "atomos.service.contract";
+ private static final String BSN_SERVICE_IMPL = "org.apache.felix.atomos.tests.testbundles.service.impl";
@Test
void testConnectLocation(@TempDir Path storage)
throws BundleException, InterruptedException
@@ -1066,6 +1086,114 @@ public class ModulepathLaunchTest
}
@Test
+ void testUnmodifiableExistingHeaders(@TempDir Path storage) throws BundleException
+ {
+ AtomicBoolean fail = new AtomicBoolean(true);
+ HeaderProvider attemptModification = (location, headers) -> {
+ try
+ {
+ headers.put(Constants.BUNDLE_SYMBOLICNAME, "should.fail");
+ fail.set(true);
+ }
+ catch (UnsupportedOperationException e)
+ {
+ // expected
+ fail.set(false);
+ }
+ return Optional.empty();
+ };
+ testFramework = Atomos.newAtomos(attemptModification).newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+ if (fail.get())
+ {
+ Assertions.fail("Was able to modify the existing headers");
+ }
+ }
+
+ @Test
+ void testModuleWithCustomerHeader(@TempDir Path storage) throws BundleException, InterruptedException
+ {
+ HeaderProvider provider = (location, headers) -> {
+ headers = new HashMap<>(headers);
+ headers.put("X-TEST", location);
+ return Optional.of(headers);
+ };
+ testFramework = getFramework(null, provider,
+ Constants.FRAMEWORK_STORAGE + '=' + storage.toFile().getAbsolutePath());
+
+ // make sure the contract names are correct
+ Module contractModule = Echo.class.getModule();
+ Bundle contractBundle = FrameworkUtil.getBundle(Echo.class);
+ assertEquals(BSN_CONTRACT, contractBundle.getSymbolicName(),
+ "Wrong BSN for contract bundle.");
+ assertEquals(Echo.class.getPackageName(), contractModule.getName(),
+ "Wrong module name for contract module.");
+
+ assertEquals(contractBundle.getLocation(), "atomos:" + contractBundle.getHeaders().get("X-TEST"));
+
+ testFramework.stop();
+ testFramework.waitForStop(10000);
+
+ // Bundles should already be installed, disable auto-install option
+ // and check the provider is still used to provide the custom header
+ // for the already installed bundle from persistence
+ testFramework = Atomos.newAtomos(Map.of(Atomos.ATOMOS_CONTENT_INSTALL, "false"),
+ provider).newFramework(
+ Map.of(Constants.FRAMEWORK_STORAGE, storage.toFile().getAbsolutePath()));
+ testFramework.start();
+ Bundle contractBundle2 = FrameworkUtil.getBundle(Echo.class);
+ assertNotEquals(contractBundle, contractBundle2, "Expecting new bundle.");
+ assertEquals(contractBundle.getLocation(),
+ "atomos:" + contractBundle.getHeaders().get("X-TEST"));
+ }
+
+ @Test
+ void testHeaderProviderChangeBSN(@TempDir Path storage) throws BundleException
+ {
+ final String CHANGED_BSN = "changed.bsn";
+ HeaderProvider changeBSN = (location, headers) -> {
+ if (BSN_CONTRACT.equals(headers.get(Constants.BUNDLE_SYMBOLICNAME)))
+ {
+ headers = new HashMap<>(headers);
+ headers.put(Constants.BUNDLE_SYMBOLICNAME, CHANGED_BSN);
+ headers.put(Constants.BUNDLE_VERSION, "100");
+ return Optional.of(headers);
+ }
+ return Optional.empty();
+ };
+ testFramework = getFramework(null, changeBSN,
+ Constants.FRAMEWORK_STORAGE + '=' + storage.toFile().getAbsolutePath());
+ BundleContext bc = testFramework.getBundleContext();
+ Atomos atomos = bc.getService(bc.getServiceReference(Atomos.class));
+
+ // make sure the contract names are correct
+ Module contractModule = Echo.class.getModule();
+ Bundle contractBundle = FrameworkUtil.getBundle(Echo.class);
+ assertEquals(CHANGED_BSN, contractBundle.getSymbolicName(),
+ "Wrong BSN for contract bundle.");
+ assertEquals(Version.valueOf("100"), contractBundle.getVersion());
+ assertEquals(Echo.class.getPackageName(), contractModule.getName(),
+ "Wrong module name for contract module.");
+
+ // make sure the bundle wiring reflect the mapping correctly using the BSN
+ Bundle testBundle = FrameworkUtil.getBundle(ModulepathLaunch.class);
+ BundleWiring testWiring = testBundle.adapt(BundleWiring.class);
+ assertTrue(testWiring.getRequiredWires(BundleNamespace.BUNDLE_NAMESPACE).stream() //
+ .filter((w) -> CHANGED_BSN.equals( //
+ w.getCapability().getAttributes().get( //
+ BundleNamespace.BUNDLE_NAMESPACE))) //
+ .findFirst().isPresent(), "No wire for " + CHANGED_BSN);
+
+ atomos.getBootLayer().findAtomosContent(CHANGED_BSN).ifPresentOrElse((c) -> {
+ assertEquals(CHANGED_BSN, c.getSymbolicName());
+ assertEquals(Version.valueOf("100"), c.getVersion());
+ }, () -> {
+ fail("Could not find the content: " + CHANGED_BSN);
+ });
+ }
+
+ @Test
void testMultiParentResolve(@TempDir Path storage) throws BundleException
{
testFramework = getFramework(null,
diff --git a/atomos/pom.xml b/atomos/pom.xml
index a593ede..c0c7a18 100644
--- a/atomos/pom.xml
+++ b/atomos/pom.xml
@@ -47,6 +47,22 @@
</activation>
</profile>
<profile>
+ <id>unit-test</id>
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.platform</groupId>
+ <artifactId>org.eclipse.osgi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix.atomos.equinox</groupId>
+ <artifactId>osgi.core</artifactId>
+ <version>8.0.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
<id>equinox</id>
<dependencies>
<dependency>
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..3b9f495 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/Atomos.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/Atomos.java
@@ -13,17 +13,24 @@
*/
package org.apache.felix.atomos;
+import static org.apache.felix.atomos.impl.base.AtomosBase.NO_OP_HEADER_PROVIDER;
+
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.ServiceLoader;
+import java.util.function.BiFunction;
import org.apache.felix.atomos.AtomosLayer.LoaderType;
import org.apache.felix.atomos.impl.base.AtomosBase;
+import org.osgi.annotation.versioning.ConsumerType;
+import org.osgi.annotation.versioning.ProviderType;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
+import org.osgi.framework.connect.ConnectContent;
import org.osgi.framework.connect.ConnectFrameworkFactory;
import org.osgi.framework.connect.ModuleConnector;
import org.osgi.framework.launch.Framework;
@@ -134,6 +141,7 @@ import org.osgi.framework.launch.Framework;
* service registered with its bundle context.
*/
@org.osgi.annotation.bundle.Header(name = "Main-Class", value = "org.apache.felix.atomos.Atomos")
+@ProviderType
public interface Atomos
{
/**
@@ -205,7 +213,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 +244,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)
@@ -264,7 +272,33 @@ public interface Atomos
*/
static Atomos newAtomos()
{
- return newAtomos(Collections.emptyMap());
+ return newAtomos(NO_OP_HEADER_PROVIDER);
+ }
+
+ /**
+ * Creates a new Atomos that can be used to create a new OSGi framework
+ * instance. Same as calling {@code newAtomos(Map,HeaderProvider)} with an empty
+ * configuration.
+ *
+ * @param headerProvider the header provider function
+ * @return a new Atomos.
+ */
+ static Atomos newAtomos(HeaderProvider headerProvider)
+ {
+ return newAtomos(Collections.emptyMap(), headerProvider);
+ }
+
+ /**
+ * Creates a new Atomos that can be used to create a new OSGi framework
+ * instance. Same as calling {@code newAtomos(Map,HeaderProvider)} with a
+ * no-op {@code headerProvider} function.
+ *
+ * @param configuration the properties to configure the new Atomos
+ * @return a new Atomos.
+ */
+ static Atomos newAtomos(Map<String, String> configuration)
+ {
+ return newAtomos(configuration, NO_OP_HEADER_PROVIDER);
}
/**
@@ -277,15 +311,61 @@ public interface Atomos
* 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
+ * Note that this {@code Atomos} must be used for creating a new framework instance
+ * with the method {@link ConnectFrameworkFactory#newFramework(Map, ModuleConnector)} to use
+ * the layers added to this {@code Atomos} or the {@link #newFramework(Map)} method can
+ * be called on this {@code Atomos}.
+ * <p>
+ * The given headerProvider function maps each Atomos content
+ * {@link AtomosContent#getAtomosLocation() location}
+ * and existing headers of the content to a new optional map of headers.
+ * The resulting map will be used as the headers for the {@link ConnectContent#getHeaders()}.
+ * If the function returns an empty optional then the existing
+ * headers will be used.
+ *
+ * @param configuration the properties to configure the new Atomos
+ * @param headerProvider a function that will be called with the location and the existing headers for each Atomos content.
* @return a new Atomos.
*/
- static Atomos newAtomos(Map<String, String> configuration)
+ static Atomos newAtomos(Map<String, String> configuration,
+ HeaderProvider headerProvider)
{
- return AtomosBase.newAtomos(configuration);
+ return AtomosBase.newAtomos(configuration, headerProvider);
+ }
+
+ /**
+ * A function that maps each {@code AtomosContent} {@link AtomosContent#getAtomosLocation() location}
+ * and its existing headers to a new optional map of headers to be used for the
+ * {@link ConnectContent#getHeaders() headers} of the {@link AtomosContent#getConnectContent() ConnectContent}.
+ */
+ @FunctionalInterface
+ @ConsumerType
+ interface HeaderProvider extends BiFunction<String, Map<String, String>, Optional<Map<String, String>>>
+ {
+
+ /**
+ * Applies this header provider function to the specified
+ * {@code AtomosContent} {@link AtomosContent#getAtomosLocation() location} and
+ * map of existing headers. The returned {@code Optional} map of headers will
+ * be used by the {@link ConnectContent#getHeaders()} method for the
+ * {@code ConnectContent} {@link AtomosContent#getConnectContent() associated}
+ * with the {@code AtomosContent} that has the specified
+ * {@link AtomosContent#getAtomosLocation() location}.
+ * <p>
+ * This method allows a header provider to augment existing bundle manifest
+ * headers or add completely new bundle manifest headers that are not present
+ * in the existing headers.
+ * <p>
+ * This function may be applied before the instance of the {@code AtomosContent}
+ * instance is created which may result in the symbolic name and or version
+ * of the {@code AtomosContent} to be influenced by this function.
+ * @param location The {@code AtomosContent} {@link AtomosContent#getAtomosLocation() location}
+ * @param existingHeaders The existing headers found for the {@code AtomosContent}
+ * @return the {@code Optional} map of headers to use instead of the {@code existingHeaders}. If
+ * the existing headers should be used then an empty {@code Optional} may be returned.
+ */
+ @Override
+ Optional<Map<String, String>> apply(String location,
+ Map<String, String> existingHeaders);
}
}
diff --git a/atomos/src/main/java/org/apache/felix/atomos/AtomosContent.java b/atomos/src/main/java/org/apache/felix/atomos/AtomosContent.java
index 660e50a..97e640c 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/AtomosContent.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/AtomosContent.java
@@ -15,6 +15,7 @@ package org.apache.felix.atomos;
import java.util.Optional;
+import org.osgi.annotation.versioning.ProviderType;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
@@ -26,6 +27,7 @@ import org.osgi.framework.connect.ConnectContent;
* by the Atomos runtime which can be installed as a connected
* bundle into an OSGi Framework.
*/
+@ProviderType
public interface AtomosContent extends Comparable<AtomosContent>
{
diff --git a/atomos/src/main/java/org/apache/felix/atomos/AtomosLayer.java b/atomos/src/main/java/org/apache/felix/atomos/AtomosLayer.java
index 0fc64da..d407248 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/AtomosLayer.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/AtomosLayer.java
@@ -19,6 +19,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import org.osgi.annotation.versioning.ProviderType;
import org.osgi.framework.BundleException;
import org.osgi.framework.connect.ConnectFrameworkFactory;
import org.osgi.framework.connect.ModuleConnector;
@@ -31,11 +32,13 @@ import org.osgi.framework.connect.ModuleConnector;
* then be used to {@link AtomosContent#install(String) install } them as OSGi connected bundles into the
* {@link ConnectFrameworkFactory#newFramework(Map, ModuleConnector)} framework}.
*/
+@ProviderType
public interface AtomosLayer
{
/**
* The loader type used for the class loaders of an Atomos layer.
*/
+ @ProviderType
enum LoaderType
{
/**
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..231786b 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
@@ -16,8 +16,10 @@ package org.apache.felix.atomos.impl.base;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
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;
@@ -52,6 +54,7 @@ import org.apache.felix.atomos.AtomosLayer;
import org.apache.felix.atomos.AtomosLayer.LoaderType;
import org.apache.felix.atomos.impl.base.AtomosBase.AtomosLayerBase.AtomosContentBase;
import org.apache.felix.atomos.impl.base.AtomosBase.AtomosLayerBase.AtomosContentIndexed;
+import org.apache.felix.atomos.impl.base.AtomosBase.AtomosLayerBase.ManifestHolder;
import org.apache.felix.atomos.impl.content.ConnectContentCloseableJar;
import org.apache.felix.atomos.impl.content.ConnectContentFile;
import org.apache.felix.atomos.impl.content.ConnectContentIndexed;
@@ -67,6 +70,7 @@ import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.framework.Version;
import org.osgi.framework.connect.ConnectContent;
+import org.osgi.framework.connect.ConnectContent.ConnectEntry;
import org.osgi.framework.connect.ConnectFrameworkFactory;
import org.osgi.framework.connect.FrameworkUtilHelper;
import org.osgi.framework.connect.ModuleConnector;
@@ -98,6 +102,8 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
public static final String ATOMOS_RUNTIME_MODULES_CLASS = "org.apache.felix.atomos.impl.modules.AtomosModules";
public static final String ATOMOS_LIB_DIR = "atomos_lib";
public static final String GRAAL_NATIVE_IMAGE_KIND = "org.graalvm.nativeimage.kind";
+ public static final HeaderProvider NO_OP_HEADER_PROVIDER = (
+ l, h) -> Optional.empty();
private final boolean DEBUG;
private final boolean REPORT_RESOLUTION_ERRORS;
@@ -132,47 +138,59 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
protected final AtomicLong nextLayerId = new AtomicLong(0);
+ protected final HeaderProvider headerProvider;
+
public static enum Index
{
IGNORE, FIRST
}
- public static Atomos newAtomos(Map<String, String> config)
+ public static Atomos newAtomos(Map<String, String> config,
+ HeaderProvider 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, HeaderProvider headerProvider)
{
try
{
return (AtomosBase) Class.forName(
- runtimeClass).getConstructor(Map.class).newInstance(config);
+ runtimeClass).getConstructor(Map.class, HeaderProvider.class).newInstance(
+ config, headerProvider);
}
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 +202,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, HeaderProvider 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 +753,69 @@ 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), //
+ (dontClose) -> {}, //
+ 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 = getRawHeaders(connectContent);
+ headers = applyHeaderProvider(holder, location, 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,41 +855,52 @@ 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(
- "META-INF/services/org.osgi.framework.launch.FrameworkFactory").isPresent())
+ try
{
- location = Constants.SYSTEM_BUNDLE_LOCATION;
+ if (connectContent.getEntry(
+ "META-INF/services/org.osgi.framework.launch.FrameworkFactory").isPresent())
+ {
+ location = Constants.SYSTEM_BUNDLE_LOCATION;
+ }
+ else
+ {
+ location = f.getName();
+ if (!getName().isEmpty())
+ {
+ location = getName() + ":" + location;
+ }
+ }
}
- else
+ finally
+ {
+ connectContent.close();
+ }
+ Map<String, String> headers = toMap(jar.getManifest());
+ headers = applyHeaderProvider(holder, location, headers);
+
+ String symbolicName = headers.get(
+ Constants.BUNDLE_SYMBOLICNAME);
+ if (symbolicName != null)
{
- location = f.getName();
- if (!getName().isEmpty())
+ int semiColon = symbolicName.indexOf(';');
+ if (semiColon != -1)
{
- location = getName() + ":" + location;
+ 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);
}
- Version version = Version.parseVersion(
- headers.getValue(Constants.BUNDLE_VERSION));
- AtomosContentBase bundle = new AtomosContentIndexed(location,
- symbolicName, version, connectContent);
- bootBundles.add(bundle);
- }
}
catch (IOException e)
{
@@ -879,13 +914,35 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
String currentIndex,
String currentBSN, Version currentVersion, List<String> currentPaths)
{
+ ManifestHolder holder = new ManifestHolder();
String bundleIndexPath = indexRoot + currentIndex;
ConnectContentIndexed content = new ConnectContentIndexed(bundleIndexPath,
- currentPaths);
+ currentPaths, holder::getHeaders);
debug("Found indexed content: %s %s %s %s", currentIndex, currentBSN,
currentVersion, currentPaths);
- return new AtomosContentIndexed(getIndexedLocation(content, currentBSN),
- currentBSN, currentVersion, content);
+ String location = getIndexedLocation(content, currentBSN);
+ if (headerProvider != NO_OP_HEADER_PROVIDER)
+ {
+ Map<String, String> headers = applyHeaderProvider(holder, location,
+ getRawHeaders(content));
+ String symbolicName = headers.get(Constants.BUNDLE_SYMBOLICNAME);
+ if (symbolicName == null)
+ {
+ throw new IllegalStateException(
+ "Expecting a symbolic name for index bundle: " + currentBSN);
+ }
+ int semiColon = symbolicName.indexOf(';');
+ if (semiColon != -1)
+ {
+ symbolicName = symbolicName.substring(0, semiColon);
+ }
+ symbolicName = symbolicName.trim();
+ currentBSN = symbolicName;
+ currentVersion = Version.parseVersion(
+ headers.get(Constants.BUNDLE_VERSION));
+ }
+ return new AtomosContentIndexed(location, currentBSN, currentVersion,
+ content);
}
private void findAtomosIndexedContent(URL index,
@@ -1395,6 +1452,52 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
return this;
}
}
+
+ public final class ManifestHolder {
+ private volatile Optional<Map<String, String>> headers = Optional.empty();
+
+ public Map<String, String> setHeaders(Optional<Map<String, String>> headers)
+ {
+ this.headers = headers;
+ return headers.get();
+ }
+
+ public Optional<Map<String, String>> getHeaders() {
+ return headers;
+ }
+ }
+ }
+
+ static protected Map<String, String> getRawHeaders(ConnectContent content)
+ {
+ return content.getEntry("META-INF/MANIFEST.MF").map(
+ AtomosBase::getRawHeaders).orElse(new HashMap<>());
+ }
+
+ static protected Map<String, String> getRawHeaders(ConnectEntry mfEntry)
+ {
+ try (InputStream in = mfEntry.getInputStream())
+ {
+ return toMap(new Manifest(in));
+ }
+ catch (IOException e)
+ {
+ sneakyThrow(e);
+ return null;
+ }
+ }
+
+ static 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
@@ -1726,4 +1829,18 @@ public abstract class AtomosBase implements Atomos, SynchronousBundleListener, F
{
// do nothing by default
}
+
+ protected Map<String, String> applyHeaderProvider(ManifestHolder holder,
+ String location,
+ Map<String, String> existingHeaders)
+ {
+ Optional<Map<String,String>> provided = headerProvider.apply(location,
+ Collections.unmodifiableMap(existingHeaders));
+ Map<String, String> headers = existingHeaders;
+ if (provided.isPresent())
+ {
+ headers = new HashMap<>(provided.get());
+ }
+ return holder.setHeaders(Optional.of(headers));
+ }
}
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..82cc7ba 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
@@ -34,9 +34,9 @@ public class AtomosClassPath extends AtomosBase
private final AtomosLayer bootLayer = createBootLayer();
- public AtomosClassPath(Map<String, String> config)
+ public AtomosClassPath(Map<String, String> config, HeaderProvider 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/ConnectContentIndexed.java b/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentIndexed.java
index 964957d..9b35736 100644
--- a/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentIndexed.java
+++ b/atomos/src/main/java/org/apache/felix/atomos/impl/content/ConnectContentIndexed.java
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Supplier;
import org.osgi.framework.connect.ConnectContent;
@@ -80,17 +81,19 @@ public class ConnectContentIndexed implements ConnectContent
private final String index;
private final Set<String> entries;
+ final Supplier<Optional<Map<String, String>>> headers;
- public ConnectContentIndexed(String index, List<String> entries)
+ public ConnectContentIndexed(String index, List<String> entries, Supplier<Optional<Map<String, String>>> headers)
{
this.index = index;
this.entries = Collections.unmodifiableSet(new LinkedHashSet<>(entries));
+ this.headers = headers;
}
@Override
public Optional<Map<String, String>> getHeaders()
{
- return Optional.empty();
+ return headers.get();
}
@Override
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..95e53f5 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
@@ -46,10 +48,7 @@ public class ConnectContentJar implements ConnectContent
@Override
public void close() throws IOException
{
- if (closer != null)
- {
- closer.accept(zipSupplier);
- }
+ closer.accept(zipSupplier);
}
@Override
@@ -93,7 +92,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..e6adf68 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
@@ -15,16 +15,14 @@ package org.apache.felix.atomos.impl.modules;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Requires;
import java.lang.module.ModuleFinder;
-import java.lang.module.ModuleReader;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.nio.file.Path;
-import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -33,40 +31,43 @@ import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
import java.util.stream.Collectors;
+import org.apache.felix.atomos.Atomos;
import org.apache.felix.atomos.AtomosContent;
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
{
+ private final static String ATOMOS_GENERATED = "Atomos-GeneratedManifest";
+ private final static String ATOMOS_TEMPORARY_GENERATED_REQUIRES = "Atomos-TemporaryGeneratedRequires";
private final Module thisModule = AtomosModules.class.getModule();
private final Configuration thisConfig = thisModule.getLayer() == null ? null
: thisModule.getLayer().configuration();
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, HeaderProvider headerProvider)
{
- super(config);
+ super(config, headerProvider);
}
private AtomosLayer createBootLayer()
@@ -284,67 +285,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 +337,28 @@ public class AtomosModules extends AtomosBase
a -> a.adapt(Module.class).isPresent()).collect(
Collectors.toUnmodifiableMap((k) -> k.adapt(Module.class).get(),
(v) -> v));
+ for (AtomosContentBase content : atomosBundles)
+ {
+ final Module m = content.adapt(Module.class).orElse(null);
+ if (m == null)
+ {
+ continue;
+ }
+ content.getConnectContent().getHeaders().ifPresent(headers -> {
+ if (Boolean.parseBoolean(headers.get(ATOMOS_GENERATED)) &&
+ Boolean.parseBoolean(headers.get(ATOMOS_TEMPORARY_GENERATED_REQUIRES)))
+ {
+ calculateRequires(headers, m, (requires) -> {
+ return m.getLayer().findModule(requires).map(
+ requiredModule -> {
+ return getAtomosContent(
+ requiredModule).getSymbolicName();
+ }).orElse(requires);
+ });
+ headers.remove(ATOMOS_TEMPORARY_GENERATED_REQUIRES);
+ }
+ });
+ }
}
@Override
@@ -450,6 +412,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,15 +446,209 @@ 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();
+ try
+ {
+ headers = getRawHeaders(content);
+ }
+ finally
+ {
+ content.close();
+ }
+ }
+ catch (IOException e)
+ {
+ throw new UncheckedIOException("Error reading connect manifest.", e);
+ }
+
+
+
+ generateHeaders(headers, m);
+
+ headers = applyHeaderProvider(holder, location, 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));
+ found.add(new AtomosContentModule(m, location,
+ symbolicName, version, content));
+ }
}
return Collections.unmodifiableSet(found);
}
+ private void generateHeaders(Map<String, String> headers, Module m)
+ {
+ ModuleDescriptor desc = m.getDescriptor();
+ StringBuilder capabilities = new StringBuilder();
+ StringBuilder requirements = new StringBuilder();
+ String bsn = headers.get(Constants.BUNDLE_SYMBOLICNAME);
+ if (bsn == null)
+ {
+ // 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,
+ desc.name() + "; " + Constants.FRAGMENT_ATTACHMENT_DIRECTIVE + ":="
+ + Constants.FRAGMENT_ATTACHMENT_NEVER);
+
+ // set the version
+ Version v;
+ try
+ {
+ v = Version.parseVersion(desc.version().map(
+ java.lang.module.ModuleDescriptor.Version::toString).orElse("0"));
+ }
+ catch (IllegalArgumentException e)
+ {
+ v = Version.emptyVersion;
+ }
+ headers.put(Constants.BUNDLE_VERSION, v.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());
+ }
+
+ // Note that for generated manifests based of module descriptor
+ // will have their requires calculated later.
+ // Place a header indicating this is generated
+ headers.put(ATOMOS_GENERATED, Boolean.TRUE.toString());
+ if (calculateRequires(headers, m, Function.identity()))
+ {
+ // Set a temporary header if we need to recalculate later
+ headers.put(ATOMOS_TEMPORARY_GENERATED_REQUIRES, Boolean.TRUE.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());
+ }
+ }
+
+ private boolean calculateRequires(Map<String, String> headers, Module m,
+ Function<String, String> mapper)
+ {
+ // only do requires for non bundle modules
+ // map requires to require bundle
+ StringBuilder requireBundleHeader = new StringBuilder();
+ for (Requires requires : m.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 mapping = mapper.apply(requires.name());
+ requireBundleHeader.append(mapping).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)
+ {
+ headers.put(Constants.REQUIRE_BUNDLE, requireBundleHeader.toString());
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
@SuppressWarnings("unchecked")
@Override
public <T> Optional<T> adapt(Class<T> type)
@@ -516,11 +673,9 @@ public class AtomosModules extends AtomosBase
*/
private final Module module;
- public AtomosContentModule(ResolvedModule resolvedModule, Module module, String location, String symbolicName, Version version)
+ public AtomosContentModule(Module module, String location, String symbolicName, Version version, ConnectContent content)
{
- super(location, symbolicName, version, new ConnectContentModule(module,
- resolvedModule.reference(), AtomosLayerModules.this, symbolicName,
- version));
+ super(location, symbolicName, version, content);
this.module = module;
}
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;