[FELIX-3239] PackageAdmin#getExportedPackages does not work on packages exported by attached fragments
This commit fixes the issue.
Unit test included.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1597276 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index d670d78..3d7c71b 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -64,10 +64,12 @@
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.launch.Framework;
+import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.startlevel.FrameworkStartLevel;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleRevisions;
+import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.service.packageadmin.ExportedPackage;
@@ -3747,22 +3749,40 @@
// newest to oldest. We assume that the first revision found to
// be exporting the package is the provider of the package,
// which makes sense since it must have been resolved first.
- List<BundleRevision> revisions =
+ List<BundleRevision> originRevisions =
bundle.adapt(BundleRevisions.class).getRevisions();
- for (int i = revisions.size() - 1; i >= 0; i--)
+ for (int i = originRevisions.size() - 1; i >= 0; i--)
{
- BundleRevision br = revisions.get(i);
- List<BundleCapability> caps = (br.getWiring() == null)
- ? br.getDeclaredCapabilities(null)
- : br.getWiring().getCapabilities(null);
- for (BundleCapability cap : caps)
+ BundleRevision originBr = originRevisions.get(i);
+ List<BundleRevision> revisions = Collections.singletonList(originBr);
+
+ if ((originBr.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0)
{
- if (cap.getNamespace().equals(req.getNamespace())
- && CapabilitySet.matches(cap, req.getFilter()))
+ // If it's a fragment, find the revisions of the attached
+ // bundle(s) and work with those instead. Note that fragments
+ // can be attached to multiple hosts...
+ revisions = new ArrayList<BundleRevision>();
+
+ for (BundleWire bw : originBr.getWiring().getRequiredWires(HostNamespace.HOST_NAMESPACE))
{
- pkgs.add(
- new ExportedPackageImpl(
- this, (BundleImpl) bundle, br, cap));
+ revisions.add(bw.getProviderWiring().getRevision());
+ }
+ }
+
+ for (BundleRevision br : revisions)
+ {
+ List<BundleCapability> caps = (br.getWiring() == null)
+ ? br.getDeclaredCapabilities(null)
+ : br.getWiring().getCapabilities(null);
+ for (BundleCapability cap : caps)
+ {
+ if (cap.getNamespace().equals(req.getNamespace())
+ && CapabilitySet.matches(cap, req.getFilter()))
+ {
+ pkgs.add(
+ new ExportedPackageImpl(
+ this, (BundleImpl) br.getBundle(), br, cap));
+ }
}
}
}
diff --git a/framework/src/test/java/org/apache/felix/framework/PackageAdminImplTest.java b/framework/src/test/java/org/apache/felix/framework/PackageAdminImplTest.java
new file mode 100644
index 0000000..8442f20
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/PackageAdminImplTest.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import junit.framework.TestCase;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.service.packageadmin.ExportedPackage;
+
+public class PackageAdminImplTest extends TestCase
+{
+ private File tempDir;
+ private Felix felix;
+ private File cacheDir;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ tempDir = File.createTempFile("felix-temp", ".dir");
+ assertTrue("precondition", tempDir.delete());
+ assertTrue("precondition", tempDir.mkdirs());
+
+ cacheDir = new File(tempDir, "felix-cache");
+ assertTrue("precondition", cacheDir.mkdir());
+
+ String cache = cacheDir.getPath();
+
+ Map<String,String> params = new HashMap<String, String>();
+ params.put("felix.cache.profiledir", cache);
+ params.put("felix.cache.dir", cache);
+ params.put(Constants.FRAMEWORK_STORAGE, cache);
+
+ felix = new Felix(params);
+ felix.init();
+ felix.start();
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+
+ felix.stop(); // Note that this method is async
+ felix = null;
+
+ deleteDir(tempDir);
+ tempDir = null;
+ cacheDir = null;
+ }
+
+ public void testExportedPackages() throws Exception
+ {
+ String bmf = "Bundle-SymbolicName: pkg.bundle\n"
+ + "Bundle-Version: 1\n"
+ + "Bundle-ManifestVersion: 2\n"
+ + "Export-Package: org.foo.bundle\n";
+ File bundleFile = createBundle(bmf);
+
+ String fmf = "Bundle-SymbolicName: pkg.frag\n"
+ + "Bundle-Version: 1\n"
+ + "Fragment-Host: pkg.bundle\n"
+ + "Bundle-ManifestVersion: 2\n"
+ + "Export-Package: org.foo.fragment;version=\"2.0.0\"\n";
+ File fragFile = createBundle(fmf);
+
+ Bundle b = felix.getBundleContext().installBundle(bundleFile.toURI().toASCIIString());
+ Bundle f = felix.getBundleContext().installBundle(fragFile.toURI().toASCIIString());
+ b.start();
+
+ try
+ {
+ PackageAdminImpl pa = new PackageAdminImpl(felix);
+ assertEquals(b, pa.getExportedPackage("org.foo.bundle").getExportingBundle());
+ assertEquals(b, pa.getExportedPackage("org.foo.fragment").getExportingBundle());
+
+ Set<String> expected = new HashSet<String>();
+ expected.addAll(Arrays.asList("org.foo.bundle", "org.foo.fragment"));
+
+ Set<String> actual = new HashSet<String>();
+ for (ExportedPackage ep : pa.getExportedPackages(b))
+ {
+ actual.add(ep.getName());
+ assertEquals(b, ep.getExportingBundle());
+ }
+ assertEquals(expected, actual);
+
+ ExportedPackage[] bundlePkgs = pa.getExportedPackages("org.foo.bundle");
+ assertEquals(1, bundlePkgs.length);
+ assertEquals(b, bundlePkgs[0].getExportingBundle());
+ assertEquals(new Version("0"), bundlePkgs[0].getVersion());
+
+ ExportedPackage[] fragPkgs = pa.getExportedPackages("org.foo.fragment");
+ assertEquals(1, fragPkgs.length);
+ assertEquals("The fragment package should be exposed through the bundle",
+ b, fragPkgs[0].getExportingBundle());
+ assertEquals(new Version("2"), fragPkgs[0].getVersion());
+ }
+ finally
+ {
+ b.stop();
+ b.uninstall();
+ f.uninstall();
+ }
+ }
+
+ private File createBundle(String manifest) throws IOException
+ {
+ File f = File.createTempFile("felix-bundle", ".jar", tempDir);
+
+ Manifest mf = new Manifest(new ByteArrayInputStream(manifest.getBytes("UTF-8")));
+ mf.getMainAttributes().putValue("Manifest-Version", "1.0");
+ JarOutputStream os = new JarOutputStream(new FileOutputStream(f), mf);
+ os.close();
+ return f;
+ }
+
+ private static void deleteDir(File root) throws IOException
+ {
+ if (root.isDirectory())
+ {
+ for (File file : root.listFiles())
+ {
+ deleteDir(file);
+ }
+ }
+ root.delete();
+ }
+}