Implement osgi.identity namespace for fragments. Fixes FELIX-4324.



git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1555700 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
index e139fbd..796bc83 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
@@ -40,6 +40,7 @@
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.TreeSet;
+
 import org.apache.felix.framework.cache.Content;
 import org.apache.felix.framework.cache.JarContent;
 import org.apache.felix.framework.capabilityset.SimpleFilter;
@@ -63,6 +64,7 @@
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.hooks.weaving.WeavingException;
 import org.osgi.framework.hooks.weaving.WeavingHook;
+import org.osgi.framework.namespace.IdentityNamespace;
 import org.osgi.framework.wiring.BundleCapability;
 import org.osgi.framework.wiring.BundleRequirement;
 import org.osgi.framework.wiring.BundleRevision;
@@ -254,19 +256,32 @@
         // Calculate resolved list of capabilities, which includes:
         // 1. All capabilities from host and any fragments except for exported
         //    packages that we have an import (i.e., the export was substituted).
-        // And nothing else at this time. Fragments currently have no capabilities.
+        // 2. For fragments the identity capability only.
+        // And nothing else at this time.
         boolean isFragment = Util.isFragment(revision);
-        List<BundleCapability> capList = (isFragment)
-            ? Collections.EMPTY_LIST
-            : new ArrayList<BundleCapability>();
+        List<BundleCapability> capList = new ArrayList<BundleCapability>();
         // Also keep track of whether any resolved package capabilities are filtered.
         Map<String, List<List<String>>> includedPkgFilters =
             new HashMap<String, List<List<String>>>();
         Map<String, List<List<String>>> excludedPkgFilters =
             new HashMap<String, List<List<String>>>();
-// TODO: OSGi R4.4 - Fragments currently have no capabilities, but they may
-//       have an identity capability in the future.
-        if (!isFragment)
+
+        if (isFragment)
+        {
+            // This is a fragment, add its identity capability
+            for (BundleCapability cap : m_revision.getDeclaredCapabilities(null))
+            {
+                if (IdentityNamespace.IDENTITY_NAMESPACE.equals(cap.getNamespace()))
+                {
+                    String effective = cap.getDirectives().get(Constants.EFFECTIVE_DIRECTIVE);
+                    if ((effective == null) || (effective.equals(Constants.EFFECTIVE_RESOLVE)))
+                    {
+                        capList.add(cap);
+                    }
+                }
+            }
+        }
+        else
         {
             for (BundleCapability cap : m_revision.getDeclaredCapabilities(null))
             {
@@ -308,8 +323,11 @@
                 {
                     for (BundleCapability cap : fragment.getDeclaredCapabilities(null))
                     {
-// TODO: OSGi R4.4 - OSGi R4.4 may introduce an identity capability, if so
-//       that will need to be excluded from here.
+                        if (IdentityNamespace.IDENTITY_NAMESPACE.equals(cap.getNamespace())) {
+                            // The identity capability is not transferred from the fragment to the bundle
+                            continue;
+                        }
+
                         if (!cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
                             || (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)
                                 && !imports.contains(cap.getAttributes()
diff --git a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
index 482bfad..1742c02 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
@@ -133,15 +133,12 @@
                         bundleCap.getDirectives(),
                         hostAttrs));
                 }
-
-                //
-                // Add the osgi.identity capability.
-                // TODO support this for fragments. The main thing with supporting this
-                // for fragments is that the identity capability should not be exposed
-                // through the host's bundle wiring.
-                //
-                capList.add(addIdentityCapability(owner, headerMap, bundleCap));
             }
+
+            //
+            // Add the osgi.identity capability.
+            //
+            capList.add(addIdentityCapability(owner, headerMap, bundleCap));
         }
 
         // Verify that bundle symbolic name is specified.
diff --git a/framework/src/test/java/org/apache/felix/framework/RequirementsCapabilitiesTest.java b/framework/src/test/java/org/apache/felix/framework/RequirementsCapabilitiesTest.java
new file mode 100644
index 0000000..8cc34e6
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/RequirementsCapabilitiesTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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.framework.launch.Framework;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+
+public class RequirementsCapabilitiesTest extends TestCase
+{
+    private File tempDir;
+    private Framework 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 testIdentityCapabilityBundleFragment() throws Exception
+    {
+        String bmf = "Bundle-SymbolicName: cap.bundle\n"
+                + "Bundle-Version: 1.2.3.Blah\n"
+                + "Bundle-ManifestVersion: 2\n"
+                + "Import-Package: org.osgi.framework\n";
+        File bundleFile = createBundle(bmf);
+
+        String fmf = "Bundle-SymbolicName: cap.frag\n"
+                + "Bundle-Version: 1.0.0\n"
+                + "Fragment-Host: cap.bundle\n"
+                + "Bundle-ManifestVersion: 2\n"
+                + "Export-Package: org.foo.bar;version=\"2.0.0\"\n"
+                + "Import-Package: org.osgi.util.tracker\n";
+        File fragFile = createBundle(fmf);
+
+        Bundle b = felix.getBundleContext().installBundle(bundleFile.toURI().toASCIIString());
+        Bundle f = felix.getBundleContext().installBundle(fragFile.toURI().toASCIIString());
+
+        // Check the bundle capabilities.
+        // First check the capabilities on the Bundle Revision, which is available on installed bundles
+        BundleRevision bbr = b.adapt(BundleRevision.class);
+        List<Capability> bwbCaps = bbr.getCapabilities("osgi.wiring.bundle");
+        assertEquals(1, bwbCaps.size());
+
+        Map<String, Object> expectedBWBAttrs = new HashMap<String, Object>();
+        expectedBWBAttrs.put("osgi.wiring.bundle", "cap.bundle");
+        expectedBWBAttrs.put("bundle-version", Version.parseVersion("1.2.3.Blah"));
+        Capability expectedBWBCap = new TestCapability("osgi.wiring.bundle",
+                expectedBWBAttrs, Collections.<String, String>emptyMap());
+        assertCapsEquals(expectedBWBCap, bwbCaps.get(0));
+
+        List<Capability> bwhCaps = bbr.getCapabilities("osgi.wiring.host");
+        assertEquals(1, bwhCaps.size());
+
+        Map<String, Object> expectedBWHAttrs = new HashMap<String, Object>();
+        expectedBWHAttrs.put("osgi.wiring.host", "cap.bundle");
+        expectedBWHAttrs.put("bundle-version", Version.parseVersion("1.2.3.Blah"));
+        Capability expectedBWHCap = new TestCapability("osgi.wiring.host",
+                expectedBWHAttrs, Collections.<String, String>emptyMap());
+        assertCapsEquals(expectedBWHCap, bwhCaps.get(0));
+
+        List<Capability> bwiCaps = bbr.getCapabilities("osgi.identity");
+        assertEquals(1, bwiCaps.size());
+
+        Map<String, Object> expectedBWIAttrs = new HashMap<String, Object>();
+        expectedBWIAttrs.put("osgi.identity", "cap.bundle");
+        expectedBWIAttrs.put("type", "osgi.bundle");
+        expectedBWIAttrs.put("version", Version.parseVersion("1.2.3.Blah"));
+        Capability expectedBWICap = new TestCapability("osgi.identity",
+                expectedBWIAttrs, Collections.<String, String>emptyMap());
+        assertCapsEquals(expectedBWICap, bwiCaps.get(0));
+
+        assertEquals("The Bundle should not directly expose osgi.wiring.package",
+                0, bbr.getCapabilities("osgi.wiring.package").size());
+
+        // Check the fragment's capabilities.
+        // First check the capabilities on the Bundle Revision, which is available on installed fragments
+        BundleRevision fbr = f.adapt(BundleRevision.class);
+        List<Capability> fwpCaps = fbr.getCapabilities("osgi.wiring.package");
+        assertEquals(1, fwpCaps.size());
+
+        Map<String, Object> expectedFWAttrs = new HashMap<String, Object>();
+        expectedFWAttrs.put("osgi.wiring.package", "org.foo.bar");
+        expectedFWAttrs.put("version", Version.parseVersion("2"));
+        expectedFWAttrs.put("bundle-symbolic-name", "cap.frag");
+        expectedFWAttrs.put("bundle-version", Version.parseVersion("1.0.0"));
+        Capability expectedFWCap = new TestCapability("osgi.wiring.package",
+                expectedFWAttrs, Collections.<String, String>emptyMap());
+        assertCapsEquals(expectedFWCap, fwpCaps.get(0));
+
+        List<Capability> fiCaps = fbr.getCapabilities("osgi.identity");
+        assertEquals(1, fiCaps.size());
+        Map<String, Object> expectedFIAttrs = new HashMap<String, Object>();
+        expectedFIAttrs.put("osgi.identity", "cap.frag");
+        expectedFIAttrs.put("type", "osgi.fragment");
+        expectedFIAttrs.put("version", Version.parseVersion("1.0.0"));
+        Capability expectedFICap = new TestCapability("osgi.identity",
+                expectedFIAttrs, Collections.<String, String>emptyMap());
+        assertCapsEquals(expectedFICap, fiCaps.get(0));
+
+        // Start the bundle. This will make the BundleWiring available on both the bundle and the fragment
+        b.start();
+
+        // Check the Bundle Wiring on the fragment. It should only contain the osgi.identity capability
+        // All the other capabilities should have migrated to the bundle's BundleWiring.
+        BundleWiring fbw = f.adapt(BundleWiring.class);
+        List<BundleCapability> fbwCaps = fbw.getCapabilities(null);
+        assertEquals("Fragment should only have 1 capability: it's osgi.identity", 1, fbwCaps.size());
+        assertCapsEquals(expectedFICap, fbwCaps.get(0));
+
+        // Check the Bundle Wiring on the bundle. It should contain all the capabilities originally on the
+        // bundle and also contain the osgi.wiring.package capability from the fragment.
+        BundleWiring bbw = b.adapt(BundleWiring.class);
+        List<BundleCapability> bwbCaps2 = bbw.getCapabilities("osgi.wiring.bundle");
+        assertEquals(1, bwbCaps2.size());
+        assertCapsEquals(expectedBWBCap, bwbCaps2.get(0));
+        List<BundleCapability> bwhCaps2 = bbw.getCapabilities("osgi.wiring.host");
+        assertEquals(1, bwhCaps2.size());
+        assertCapsEquals(expectedBWHCap, bwhCaps2.get(0));
+        List<BundleCapability> bwiCaps2 = bbw.getCapabilities("osgi.identity");
+        assertEquals(1, bwiCaps2.size());
+        assertCapsEquals(expectedBWICap, bwiCaps2.get(0));
+        List<BundleCapability> bwpCaps2 = bbw.getCapabilities("osgi.wiring.package");
+        assertEquals("Bundle should have inherited the osgi.wiring.package capability from the fragment",
+                1, bwpCaps2.size());
+        assertCapsEquals(expectedFWCap, bwpCaps2.get(0));
+    }
+
+    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 assertCapsEquals(Capability expected, Capability actual)
+    {
+        assertEquals(expected.getNamespace(), actual.getNamespace());
+        assertSubMap(expected.getAttributes(), actual.getAttributes());
+        assertSubMap(expected.getDirectives(), actual.getDirectives());
+        // We ignore the resource in the comparison
+    }
+
+    private static void assertSubMap(Map<?,?> subMap, Map<?,?> fullMap)
+    {
+        for (Map.Entry<?,?> entry : subMap.entrySet())
+        {
+            assertEquals(entry.getValue(), fullMap.get(entry.getKey()));
+        }
+    }
+
+    private static void deleteDir(File root) throws IOException
+    {
+        if (root.isDirectory())
+        {
+            for (File file : root.listFiles())
+            {
+                deleteDir(file);
+            }
+        }
+        assertTrue(root.delete());
+    }
+
+    static class TestCapability implements Capability
+    {
+        private final String namespace;
+        private final Map<String, Object> attributes;
+        private final Map<String, String> directives;
+
+        TestCapability(String ns, Map<String,Object> attrs, Map<String,String> dirs)
+        {
+            namespace = ns;
+            attributes = attrs;
+            directives = dirs;
+        }
+
+        public String getNamespace()
+        {
+            return namespace;
+        }
+
+        public Map<String, Object> getAttributes()
+        {
+            return attributes;
+        }
+
+        public Map<String, String> getDirectives()
+        {
+            return directives;
+        }
+
+        public Resource getResource()
+        {
+            return null;
+        }
+    }
+}
diff --git a/framework/src/test/java/org/apache/felix/framework/StartStopBundleTest.java b/framework/src/test/java/org/apache/felix/framework/StartStopBundleTest.java
index af50734..315606c 100644
--- a/framework/src/test/java/org/apache/felix/framework/StartStopBundleTest.java
+++ b/framework/src/test/java/org/apache/felix/framework/StartStopBundleTest.java
@@ -25,12 +25,12 @@
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.CountDownLatch;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
 import java.util.zip.ZipEntry;
 
 import junit.framework.TestCase;
+
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -41,7 +41,6 @@
 public class StartStopBundleTest extends TestCase
 {
     public static final int DELAY = 1000;
-    private File cacheDir;
 
     public void testStartStopBundle() throws Exception
     {
@@ -52,7 +51,7 @@
             + "org.osgi.service.startlevel; version=1.1.0,"
             + "org.osgi.util.tracker; version=1.3.3,"
             + "org.osgi.service.url; version=1.0.0");
-        cacheDir = File.createTempFile("felix-cache", ".dir");
+        File cacheDir = File.createTempFile("felix-cache", ".dir");
         cacheDir.delete();
         cacheDir.mkdirs();
         String cache = cacheDir.getPath();
@@ -64,66 +63,69 @@
             + "Bundle-Version: 1.1.0\n"
             + "Bundle-ManifestVersion: 2\n"
             + "Import-Package: org.osgi.framework\n";
-        File bundleFile = createBundle(mf);
+        File bundleFile = createBundle(mf, cacheDir);
 
         Framework f = new Felix(params);
         f.init();
         f.start();
 
-        final Bundle bundle = f.getBundleContext().installBundle(bundleFile.toURI().toString());
-        final CountDownLatch latch = new CountDownLatch(1);
+        try {
+            final Bundle bundle = f.getBundleContext().installBundle(bundleFile.toURI().toString());
 
-        new Thread()
-        {
-            public void run()
+            new Thread()
             {
-                try
+                public void run()
                 {
-                    bundle.start();
+                    try
+                    {
+                        bundle.start();
+                    }
+                    catch (BundleException e)
+                    {
+                        e.printStackTrace();
+                    }
                 }
-                catch (BundleException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-        Thread.sleep(DELAY / 4);
-        long t0 = System.currentTimeMillis();
-        bundle.stop();
-        long t1 = System.currentTimeMillis();
+            }.start();
+            Thread.sleep(DELAY / 4);
+            long t0 = System.currentTimeMillis();
+            bundle.stop();
+            long t1 = System.currentTimeMillis();
 
-        assertEquals(Bundle.RESOLVED, bundle.getState());
-        assertTrue((t1 - t0) > DELAY / 2);
+            assertEquals(Bundle.RESOLVED, bundle.getState());
+            assertTrue((t1 - t0) > DELAY / 2);
 
-        bundle.start();
+            bundle.start();
 
-        new Thread()
-        {
-            public void run()
+            new Thread()
             {
-                try
+                public void run()
                 {
-                    bundle.stop();
+                    try
+                    {
+                        bundle.stop();
+                    }
+                    catch (BundleException e)
+                    {
+                        e.printStackTrace();
+                    }
                 }
-                catch (BundleException e)
-                {
-                    e.printStackTrace();
-                }
-            }
-        }.start();
-        Thread.sleep(DELAY / 4);
-        t0 = System.currentTimeMillis();
-        bundle.start();
-        t1 = System.currentTimeMillis();
+            }.start();
+            Thread.sleep(DELAY / 4);
+            t0 = System.currentTimeMillis();
+            bundle.start();
+            t1 = System.currentTimeMillis();
 
-        assertEquals(Bundle.ACTIVE, bundle.getState());
-        assertTrue((t1 - t0) > DELAY / 2);
+            assertEquals(Bundle.ACTIVE, bundle.getState());
+            assertTrue((t1 - t0) > DELAY / 2);
+        } finally {
+            f.stop();
+            deleteDir(cacheDir);
+        }
     }
 
-    private static File createBundle(String manifest) throws IOException
+    private static File createBundle(String manifest, File tempDir) throws IOException
     {
-        File f = File.createTempFile("felix-bundle", ".jar");
-        f.deleteOnExit();
+        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");
@@ -143,7 +145,18 @@
         return f;
     }
 
-    public static class TestBundleActivator implements BundleActivator
+    private static void deleteDir(File root) throws IOException
+    {
+        if (root.isDirectory())
+        {
+            for (File file : root.listFiles())
+            {
+                deleteDir(file);
+            }
+        }
+        assertTrue(root.delete());
+    }
+   public static class TestBundleActivator implements BundleActivator
     {
         public void start(BundleContext context) throws Exception
         {