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";