FELIX-2382: Installing a feature with a fragment should refresh the host

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@952663 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java
index 9be2a44..178a6b5 100644
--- a/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java
+++ b/karaf/features/core/src/main/java/org/apache/felix/karaf/features/internal/FeaturesServiceImpl.java
@@ -395,6 +395,40 @@
     }
 
     protected Set<Bundle> findBundlesToRefresh(InstallationState state) {
+        Set<Bundle> bundles = new HashSet<Bundle>();
+        bundles.addAll(findBundlesWithOptionalPackagesToRefresh(state));
+        bundles.addAll(findBundlesWithFramentsToRefresh(state));
+        return bundles;
+    }
+
+    protected Set<Bundle> findBundlesWithFramentsToRefresh(InstallationState state) {
+        Set<Bundle> bundles = new HashSet<Bundle>();
+        for (Bundle b : state.installed) {
+            String hostHeader = (String) b.getHeaders().get(Constants.FRAGMENT_HOST);
+            if (hostHeader != null) {
+                List<HeaderParser.PathElement> header = HeaderParser.parseHeader(hostHeader);
+                if (header != null && header.size() > 0) {
+                    HeaderParser.PathElement path = header.get(0);
+                    for (Bundle hostBundle : state.bundles) {
+                        if (hostBundle.getSymbolicName().equals(path.getName())) {
+                            String ver = path.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE);
+                            if (ver != null) {
+                                VersionRange v = VersionRange.parse(ver);
+                                if (v.isInRange(hostBundle.getVersion())) {
+                                    bundles.add(hostBundle);
+                                }
+                            } else {
+                                bundles.add(hostBundle);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return bundles;
+    }
+
+    protected Set<Bundle> findBundlesWithOptionalPackagesToRefresh(InstallationState state) {
         // First pass: include all bundles contained in these features
         Set<Bundle> bundles = new HashSet<Bundle>(state.bundles);
         bundles.removeAll(state.installed);
diff --git a/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java
index 2e6ff37..f7b568e 100644
--- a/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java
+++ b/karaf/features/core/src/test/java/org/apache/felix/karaf/features/FeaturesServiceTest.java
@@ -22,28 +22,30 @@
 import java.io.PrintWriter;
 import java.net.MalformedURLException;
 import java.net.URI;
+import java.net.URL;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.EnumSet;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.jar.JarInputStream;
 
 import junit.framework.TestCase;
-import org.apache.felix.karaf.features.internal.FeaturesServiceImpl;
 import org.apache.felix.karaf.features.internal.FeatureImpl;
+import org.apache.felix.karaf.features.internal.FeaturesServiceImpl;
 import org.easymock.EasyMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.isA;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.packageadmin.PackageAdmin;
 import org.osgi.service.prefs.Preferences;
 import org.osgi.service.prefs.PreferencesService;
 
+import static org.easymock.EasyMock.*;
+
 public class FeaturesServiceTest extends TestCase {
 
     public void testInstallFeature() throws Exception {
@@ -392,6 +394,7 @@
         expect(installedBundle.getBundleId()).andReturn(12345L);
         expect(bundleContext.getBundle(12345L)).andReturn(installedBundle);
         expect(installedBundle.getHeaders()).andReturn(new Hashtable());
+        expect(installedBundle.getSymbolicName()).andReturn("bundle");
 
         installedBundle.start();
         
@@ -616,7 +619,7 @@
                                            isA(InputStream.class))).andReturn(installedBundle);
         expect(installedBundle.getBundleId()).andReturn(1234L);
         expect(bundleContext.getBundle(1234L)).andReturn(installedBundle);
-        expect(installedBundle.getHeaders()).andReturn(new Hashtable());
+        expect(installedBundle.getHeaders()).andReturn(new Hashtable()).anyTimes();
         installedBundle.start();
         
         expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
@@ -733,7 +736,7 @@
         expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
         expect(bundleContext.installBundle(eq(bundle2), isA(InputStream.class))).andReturn(installedBundle2);
         expect(installedBundle2.getBundleId()).andReturn(54321L);
-        expect(installedBundle2.getHeaders()).andReturn(new Hashtable());
+        expect(installedBundle2.getHeaders()).andReturn(new Hashtable()).anyTimes();
         installedBundle2.start();
 
         expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
@@ -809,7 +812,7 @@
         expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
         expect(bundleContext.installBundle(eq(bundle2), isA(InputStream.class))).andReturn(installedBundle2);
         expect(installedBundle2.getBundleId()).andReturn(54321L);
-        expect(installedBundle2.getHeaders()).andReturn(new Hashtable());
+        expect(installedBundle2.getHeaders()).andReturn(new Hashtable()).anyTimes();
         installedBundle2.start();
 
         expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
@@ -975,6 +978,100 @@
 //        verify(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle1, installedBundle2);
     }
 
+    public void testInstallFeatureWithHostToRefresh() throws Exception {
+        String bundle1 = getJarUrl(PreferencesService.class);
+        String bundle2 = getJarUrl(Bundle.class);
+
+        File tmp = File.createTempFile("smx", ".feature");
+        PrintWriter pw = new PrintWriter(new FileWriter(tmp));
+        pw.println("<features>");
+        pw.println("  <feature name='f1'>");
+        pw.println("    <bundle>" + bundle1 + "</bundle>");
+        pw.println("    <bundle>" + bundle2 + "</bundle>");
+        pw.println("  </feature>");
+        pw.println("</features>");
+        pw.close();
+
+        URI uri = tmp.toURI();
+
+        JarInputStream j = new JarInputStream(new URL(bundle1).openStream());
+        Dictionary<String,String> headers = new Hashtable();
+        for (Map.Entry e : j.getManifest().getMainAttributes().entrySet()) {
+            headers.put(e.getKey().toString(), e.getValue().toString());
+        }
+
+        // loads the state
+        Preferences prefs = EasyMock.createMock(Preferences.class);
+        PreferencesService preferencesService = EasyMock.createMock(PreferencesService.class);
+        Preferences repositoriesNode = EasyMock.createMock(Preferences.class);
+        Preferences repositoriesAvailableNode = EasyMock.createMock(Preferences.class);
+        Preferences featuresNode = EasyMock.createMock(Preferences.class);
+        PackageAdmin packageAdmin = EasyMock.createMock(PackageAdmin.class);
+        BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
+        Bundle installedBundle1 = EasyMock.createMock(Bundle.class);
+        Bundle installedBundle2 = EasyMock.createMock(Bundle.class);
+
+        // savestate from addRepository
+        expect(preferencesService.getUserPreferences("FeaturesServiceState")).andStubReturn(prefs);
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        // Installs feature f1
+        expect(installedBundle1.getBundleId()).andReturn(12345L).anyTimes();
+        expect(installedBundle1.getSymbolicName()).andReturn(headers.get(Constants.BUNDLE_SYMBOLICNAME)).anyTimes();
+        expect(installedBundle1.getHeaders()).andReturn(headers).anyTimes();
+        expect(bundleContext.getBundles()).andReturn(new Bundle[] { installedBundle1 });
+
+        expect(bundleContext.installBundle(eq(bundle2), isA(InputStream.class))).andReturn(installedBundle2);
+        expect(bundleContext.getBundles()).andReturn(new Bundle[] { installedBundle1, installedBundle2 });
+        expect(installedBundle2.getBundleId()).andReturn(54321L);
+        expect(installedBundle2.getSymbolicName()).andReturn("fragment").anyTimes();
+        Dictionary d = new Hashtable();
+        d.put(Constants.FRAGMENT_HOST, headers.get(Constants.BUNDLE_SYMBOLICNAME));
+        expect(installedBundle2.getHeaders()).andReturn(d).anyTimes();
+
+        expect(installedBundle1.getState()).andReturn(Bundle.ACTIVE);
+        expect(installedBundle1.getState()).andReturn(Bundle.ACTIVE);
+        expect(installedBundle2.getState()).andReturn(Bundle.INSTALLED);
+        expect(installedBundle2.getState()).andReturn(Bundle.INSTALLED);
+
+        //
+        // This is the real test to make sure the host is actually refreshed
+        //
+        packageAdmin.refreshPackages(aryEq(new Bundle[] { installedBundle1 }));
+
+        expect(prefs.node("repositories")).andReturn(repositoriesNode);
+        repositoriesNode.clear();
+        repositoriesNode.putInt("count", 1);
+        repositoriesNode.put("item.0", uri.toString());
+        expect(prefs.node("features")).andReturn(featuresNode);
+        featuresNode.clear();
+        featuresNode.put("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + FeatureImpl.DEFAULT_VERSION, "54321,12345");
+        prefs.putBoolean("bootFeaturesInstalled", false);
+        prefs.flush();
+
+        replay(packageAdmin, preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle1, installedBundle2);
+
+        FeaturesServiceImpl svc = new FeaturesServiceImpl();
+        svc.setPackageAdmin(packageAdmin);
+        svc.setPreferences(preferencesService);
+        svc.setBundleContext(bundleContext);
+        svc.addRepository(uri);
+
+        List<Feature> features = Arrays.asList(svc.listFeatures());
+        Collections.reverse(features);
+        svc.installFeatures(new CopyOnWriteArraySet<Feature>(features),
+                            EnumSet.noneOf(FeaturesService.Option.class));
+
+//        verify(preferencesService, prefs, repositoriesNode, featuresNode, bundleContext, installedBundle1, installedBundle2);
+    }
+
     private String getJarUrl(Class cl) {
         String name = cl.getName();
         name = name.replace(".", "/")  + ".class";