You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2014/03/04 20:31:43 UTC

git commit: [KARAF-2797] Add support of verbose and noRefresh on feature uninstall

Repository: karaf
Updated Branches:
  refs/heads/master 7e4fe0d9e -> 3c1b01d4a


[KARAF-2797] Add support of verbose and noRefresh on feature uninstall


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/3c1b01d4
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/3c1b01d4
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/3c1b01d4

Branch: refs/heads/master
Commit: 3c1b01d4aa6646a3f3dfeb8d7e4a76c105c89b1f
Parents: 7e4fe0d
Author: Jean-Baptiste Onofré <jb...@apache.org>
Authored: Tue Mar 4 20:31:00 2014 +0100
Committer: Jean-Baptiste Onofré <jb...@apache.org>
Committed: Tue Mar 4 20:31:00 2014 +0100

----------------------------------------------------------------------
 .../command/UninstallFeatureCommand.java        |  19 +-
 .../apache/karaf/features/FeaturesService.java  |   4 +
 .../karaf/features/internal/BundleManager.java  | 878 +++++++++----------
 .../features/internal/FeaturesServiceImpl.java  |  22 +-
 .../management/FeaturesServiceMBean.java        |   4 +
 .../internal/FeaturesServiceMBeanImpl.java      |  22 +-
 .../karaf/features/FeaturesServiceTest.java     |  13 +-
 .../karaf/tooling/features/InstallKarsMojo.java |   8 +
 8 files changed, 504 insertions(+), 466 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/3c1b01d4/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
----------------------------------------------------------------------
diff --git a/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java b/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
index 51c427b..e22d2f3 100644
--- a/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
+++ b/features/command/src/main/java/org/apache/karaf/features/command/UninstallFeatureCommand.java
@@ -21,8 +21,10 @@ import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.shell.commands.Completer;
+import org.apache.karaf.shell.commands.Option;
 import org.apache.karaf.shell.inject.Service;
 
+import java.util.EnumSet;
 import java.util.List;
 
 @Command(scope = "feature", name = "uninstall", description = "Uninstalls a feature with the specified name and version.")
@@ -33,8 +35,21 @@ public class UninstallFeatureCommand extends FeaturesCommandSupport {
     @Completer(InstalledFeatureCompleter.class)
     List<String> features;
 
+    @Option(name = "-r", aliases = "--no-auto-refresh", description = "Do not automatically refresh bundles", required = false, multiValued = false)
+    boolean noRefresh;
+
+    @Option(name = "-v", aliases = "--verbose", description = "Explain what is being done", required = false, multiValued = false)
+    boolean verbose;
+
     protected void doExecute(FeaturesService admin) throws Exception {
         // iterate in the provided feature
+        EnumSet<FeaturesService.Option> options = EnumSet.noneOf(FeaturesService.Option.class);
+        if (noRefresh) {
+            options.add(FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        if (verbose) {
+            options.add(FeaturesService.Option.Verbose);
+        }
         for (String feature : features) {
             String[] split = feature.split("/");
             String name = split[0];
@@ -43,9 +58,9 @@ public class UninstallFeatureCommand extends FeaturesCommandSupport {
                 version = split[1];
             }
     	    if (version != null && version.length() > 0) {
-    		    admin.uninstallFeature(name, version);
+    		    admin.uninstallFeature(name, version, options);
     	    } else {
-    		    admin.uninstallFeature(name );
+    		    admin.uninstallFeature(name, options);
     	    }
         }
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/3c1b01d4/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
index e09ffdd..9e9b08f 100644
--- a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
@@ -67,7 +67,11 @@ public interface FeaturesService {
 
     void installFeatures(Set<Feature> features, EnumSet<Option> options) throws Exception;
 
+    void uninstallFeature(String name, EnumSet<Option> options) throws Exception;
+
     void uninstallFeature(String name) throws Exception;
+
+    void uninstallFeature(String name, String version, EnumSet<Option> options) throws Exception;
     
     void uninstallFeature(String name, String version) throws Exception;
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/3c1b01d4/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java b/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java
index a0eab0c..cd744f0 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/BundleManager.java
@@ -62,458 +62,440 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class BundleManager {
-	private static final Logger LOGGER = LoggerFactory
-			.getLogger(BundleManager.class);
-	private final BundleContext bundleContext;
-	private final RegionsPersistence regionsPersistence;
-	private final long refreshTimeout;
-
-	public BundleManager(BundleContext bundleContext) {
-		this(bundleContext, null);
-	}
-
-	public BundleManager(BundleContext bundleContext,
-			RegionsPersistence regionsPersistence) {
-		this(bundleContext, regionsPersistence, 5000);
-	}
-
-	public BundleManager(BundleContext bundleContext,
-			RegionsPersistence regionsPersistence, long refreshTimeout) {
-		this.bundleContext = bundleContext;
-		this.regionsPersistence = regionsPersistence;
-		this.refreshTimeout = refreshTimeout;
-	}
-
-	public BundleInstallerResult installBundleIfNeeded(String bundleLocation,
-			int startLevel, String regionName) throws IOException,
-			BundleException {
-		BundleInstallerResult result = doInstallBundleIfNeeded(bundleLocation,
-				startLevel);
-		installToRegion(regionName, result.bundle, result.isNew);
-		return result;
-	}
-
-	private BundleInstallerResult doInstallBundleIfNeeded(
-			String bundleLocation, int startLevel) throws IOException,
-			BundleException {
-		InputStream is = getInputStreamForBundle(bundleLocation);
-		try {
-			is.mark(256 * 1024);
-			@SuppressWarnings("resource")
-			JarInputStream jar = new JarInputStream(is);
-			Manifest m = jar.getManifest();
-			if (m == null) {
-				throw new BundleException(
-						"Manifest not present in the first entry of the zip "
-								+ bundleLocation);
-			}
-			String sn = m.getMainAttributes().getValue(
-					Constants.BUNDLE_SYMBOLICNAME);
-			if (sn == null) {
-				throw new BundleException(
-						"Jar is not a bundle, no Bundle-SymbolicName "
-								+ bundleLocation);
-			}
-			// remove attributes from the symbolic name (like
-			// ;blueprint.graceperiod:=false suffix)
-			int attributeIndexSep = sn.indexOf(';');
-			if (attributeIndexSep != -1) {
-				sn = sn.substring(0, attributeIndexSep);
-			}
-			String vStr = m.getMainAttributes().getValue(
-					Constants.BUNDLE_VERSION);
-			Version v = vStr == null ? Version.emptyVersion : Version
-					.parseVersion(vStr);
-			Bundle existingBundle = findInstalled(sn, v);
-			if (existingBundle != null) {
-				LOGGER.debug("Found installed bundle: " + existingBundle);
-				return new BundleInstallerResult(existingBundle, false);
-			}
-			try {
-				is.reset();
-			} catch (IOException e) {
-				is.close();
-				is = new URL(bundleLocation).openStream();
-				// is = new BufferedInputStream(new
-				// URL(bundleLocation).openStream());
-			}
-			is = new BufferedInputStream(new FilterInputStream(is) {
-				@Override
-				public int read(byte b[], int off, int len) throws IOException {
-					if (Thread.currentThread().isInterrupted()) {
-						throw new InterruptedIOException();
-					}
-					return super.read(b, off, len);
-				}
-			});
-
-			LOGGER.debug("Installing bundle " + bundleLocation);
-			Bundle b = bundleContext.installBundle(bundleLocation, is);
-
-			if (startLevel > 0) {
-				b.adapt(BundleStartLevel.class).setStartLevel(startLevel);
-			}
-
-			return new BundleInstallerResult(b, true);
-		} finally {
-			is.close();
-		}
-	}
-
-	private Bundle findInstalled(String symbolicName, Version version) {
-		String vStr;
-		for (Bundle b : bundleContext.getBundles()) {
-			if (b.getSymbolicName() != null
-					&& b.getSymbolicName().equals(symbolicName)) {
-				vStr = (String) b.getHeaders().get(Constants.BUNDLE_VERSION);
-				Version bv = vStr == null ? Version.emptyVersion : Version
-						.parseVersion(vStr);
-				if (version.equals(bv)) {
-					return b;
-				}
-			}
-		}
-		return null;
-	}
-
-	private InputStream getInputStreamForBundle(String bundleLocation)
-			throws MalformedURLException, IOException {
-		InputStream is;
-		LOGGER.debug("Checking " + bundleLocation);
-		try {
-			int protocolIndex = bundleLocation.indexOf(":");
-			if (protocolIndex != -1) {
-				String protocol = bundleLocation.substring(0, protocolIndex);
-				waitForUrlHandler(protocol);
-			}
-			URL bundleUrl = new URL(bundleLocation);
-			is = new BufferedInputStream(bundleUrl.openStream());
-		} catch (RuntimeException e) {
-			LOGGER.error(e.getMessage());
-			throw e;
-		}
-		return is;
-	}
-
-	private void installToRegion(String region, Bundle b, boolean isNew)
-			throws BundleException {
-		if (region != null && isNew) {
-			if (regionsPersistence != null) {
-				regionsPersistence.install(b, region);
-			} else {
-				throw new RuntimeException(
-						"Unable to find RegionsPersistence service, while installing "
-								+ region);
-			}
-		}
-	}
-
-	/**
-	 * Will wait for the {@link URLStreamHandlerService} service for the
-	 * specified protocol to be registered.
-	 * 
-	 * @param protocol
-	 */
-	private void waitForUrlHandler(String protocol) {
-		try {
-			Filter filter = bundleContext.createFilter("(&("
-					+ Constants.OBJECTCLASS + "="
-					+ URLStreamHandlerService.class.getName()
-					+ ")(url.handler.protocol=" + protocol + "))");
-			if (filter == null) {
-				return;
-			}
-			ServiceTracker<URLStreamHandlerService, URLStreamHandlerService> urlHandlerTracker = new ServiceTracker<URLStreamHandlerService, URLStreamHandlerService>(
-					bundleContext, filter, null);
-			try {
-				urlHandlerTracker.open();
-				urlHandlerTracker.waitForService(30000);
-			} catch (InterruptedException e) {
-				LOGGER.debug(
-						"Interrupted while waiting for URL handler for protocol {}.",
-						protocol);
-			} finally {
-				urlHandlerTracker.close();
-			}
-		} catch (Exception ex) {
-			LOGGER.error("Error creating service tracker.", ex);
-		}
-	}
-
-	protected Set<Bundle> findBundlesToRefresh(Set<Bundle> existing,
-			Set<Bundle> installed) {
-		Set<Bundle> bundles = new HashSet<Bundle>();
-		bundles.addAll(findBundlesWithOptionalPackagesToRefresh(existing,
-				installed));
-		bundles.addAll(findBundlesWithFragmentsToRefresh(existing, installed));
-		return bundles;
-	}
-
-	protected Set<Bundle> findBundlesWithFragmentsToRefresh(
-			Set<Bundle> existing, Set<Bundle> installed) {
-		Set<Bundle> bundles = new HashSet<Bundle>();
-		Set<Bundle> oldBundles = new HashSet<Bundle>(existing);
-		oldBundles.removeAll(installed);
-		if (!oldBundles.isEmpty()) {
-			for (Bundle b : installed) {
-				String hostHeader = (String) b.getHeaders().get(
-						Constants.FRAGMENT_HOST);
-				if (hostHeader != null) {
-					Clause[] clauses = Parser.parseHeader(hostHeader);
-					if (clauses != null && clauses.length > 0) {
-						Clause path = clauses[0];
-						for (Bundle hostBundle : oldBundles) {
-							if (hostBundle.getSymbolicName().equals(
-									path.getName())) {
-								String ver = path
-										.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE);
-								if (ver != null) {
-									VersionRange v = VersionRange
-											.parseVersionRange(ver);
-									if (v.contains(hostBundle.getVersion())) {
-										bundles.add(hostBundle);
-									}
-								} else {
-									bundles.add(hostBundle);
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-		return bundles;
-	}
-
-	protected Set<Bundle> findBundlesWithOptionalPackagesToRefresh(
-			Set<Bundle> existing, Set<Bundle> installed) {
-		// First pass: include all bundles contained in these features
-		Set<Bundle> bundles = new HashSet<Bundle>(existing);
-		bundles.removeAll(installed);
-		if (bundles.isEmpty()) {
-			return bundles;
-		}
-		// Second pass: for each bundle, check if there is any unresolved
-		// optional package that could be resolved
-		Map<Bundle, List<Clause>> imports = new HashMap<Bundle, List<Clause>>();
-		for (Iterator<Bundle> it = bundles.iterator(); it.hasNext();) {
-			Bundle b = it.next();
-			String importsStr = (String) b.getHeaders().get(
-					Constants.IMPORT_PACKAGE);
-			List<Clause> importsList = getOptionalImports(importsStr);
-			if (importsList.isEmpty()) {
-				it.remove();
-			} else {
-				imports.put(b, importsList);
-			}
-		}
-		if (bundles.isEmpty()) {
-			return bundles;
-		}
-		// Third pass: compute a list of packages that are exported by our
-		// bundles and see if
-		// some exported packages can be wired to the optional imports
-		List<Clause> exports = new ArrayList<Clause>();
-		for (Bundle b : installed) {
-			String exportsStr = (String) b.getHeaders().get(
-					Constants.EXPORT_PACKAGE);
-			if (exportsStr != null) {
-				Clause[] exportsList = Parser.parseHeader(exportsStr);
-				exports.addAll(Arrays.asList(exportsList));
-			}
-		}
-		for (Iterator<Bundle> it = bundles.iterator(); it.hasNext();) {
-			Bundle b = it.next();
-			List<Clause> importsList = imports.get(b);
-			for (Iterator<Clause> itpi = importsList.iterator(); itpi.hasNext();) {
-				Clause pi = itpi.next();
-				boolean matching = false;
-				for (Clause pe : exports) {
-					if (pi.getName().equals(pe.getName())) {
-						String evStr = pe
-								.getAttribute(Constants.VERSION_ATTRIBUTE);
-						String ivStr = pi
-								.getAttribute(Constants.VERSION_ATTRIBUTE);
-						Version exported = evStr != null ? Version
-								.parseVersion(evStr) : Version.emptyVersion;
-						VersionRange imported = ivStr != null ? VersionRange
-								.parseVersionRange(ivStr)
-								: VersionRange.ANY_VERSION;
-						if (imported.contains(exported)) {
-							matching = true;
-							break;
-						}
-					}
-				}
-				if (!matching) {
-					itpi.remove();
-				}
-			}
-			if (importsList.isEmpty()) {
-				it.remove();
-			} else {
-				LOGGER.debug(
-						"Refeshing bundle {} ({}) to solve the following optional imports",
-						b.getSymbolicName(), b.getBundleId());
-				for (Clause p : importsList) {
-					LOGGER.debug("    {}", p);
-				}
-
-			}
-		}
-		return bundles;
-	}
-
-	/*
-	 * Get the list of optional imports from an OSGi Import-Package string
-	 */
-	protected List<Clause> getOptionalImports(String importsStr) {
-		Clause[] imports = Parser.parseHeader(importsStr);
-		List<Clause> result = new LinkedList<Clause>();
-		for (Clause anImport : imports) {
-			String resolution = anImport
-					.getDirective(Constants.RESOLUTION_DIRECTIVE);
-			if (Constants.RESOLUTION_OPTIONAL.equals(resolution)) {
-				result.add(anImport);
-			}
-		}
-		return result;
-	}
-
-	protected void refreshPackages(Collection<Bundle> bundles) {
-		final Object refreshLock = new Object();
-		FrameworkWiring wiring = bundleContext.getBundle().adapt(
-				FrameworkWiring.class);
-		if (wiring != null) {
-			synchronized (refreshLock) {
-				wiring.refreshBundles(bundles, new FrameworkListener() {
-					public void frameworkEvent(FrameworkEvent event) {
-						if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
-							synchronized (refreshLock) {
-								refreshLock.notifyAll();
-							}
-						}
-					}
-				});
-				try {
-					refreshLock.wait(refreshTimeout);
-				} catch (InterruptedException e) {
-					LOGGER.warn(e.getMessage(), e);
-				}
-			}
-		}
-	}
-
-	public void uninstall(Set<Bundle> bundles) {
-		for (Bundle b : bundles) {
-			try {
-				b.uninstall();
-			} catch (Exception e2) {
-				// Ignore
-			}
-		}
-		refreshPackages(null);
-	}
-
-        public void uninstall(List<Bundle> bundles) {
-                for (Bundle b : bundles) {
-                        try {
-                                b.uninstall();
-                        } catch (Exception e2) {
-                                // Ignore
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(BundleManager.class);
+    private final BundleContext bundleContext;
+    private final RegionsPersistence regionsPersistence;
+    private final long refreshTimeout;
+
+    public BundleManager(BundleContext bundleContext) {
+        this(bundleContext, null);
+    }
+
+    public BundleManager(BundleContext bundleContext, RegionsPersistence regionsPersistence) {
+        this(bundleContext, regionsPersistence, 5000);
+    }
+
+    public BundleManager(BundleContext bundleContext, RegionsPersistence regionsPersistence, long refreshTimeout) {
+        this.bundleContext = bundleContext;
+        this.regionsPersistence = regionsPersistence;
+        this.refreshTimeout = refreshTimeout;
+    }
+
+    public BundleInstallerResult installBundleIfNeeded(String bundleLocation, int startLevel, String regionName) throws IOException, BundleException {
+        BundleInstallerResult result = doInstallBundleIfNeeded(bundleLocation, startLevel);
+        installToRegion(regionName, result.bundle, result.isNew);
+        return result;
+    }
+
+    private BundleInstallerResult doInstallBundleIfNeeded(String bundleLocation, int startLevel) throws IOException, BundleException {
+        InputStream is = getInputStreamForBundle(bundleLocation);
+        try {
+            is.mark(256 * 1024);
+            @SuppressWarnings("resource")
+            JarInputStream jar = new JarInputStream(is);
+            Manifest m = jar.getManifest();
+            if (m == null) {
+                throw new BundleException("Manifest not present in the first entry of the zip " + bundleLocation);
+            }
+            String sn = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+            if (sn == null) {
+                throw new BundleException("Jar is not a bundle, no Bundle-SymbolicName " + bundleLocation);
+            }
+            // remove attributes from the symbolic name (like
+            // ;blueprint.graceperiod:=false suffix)
+            int attributeIndexSep = sn.indexOf(';');
+            if (attributeIndexSep != -1) {
+                sn = sn.substring(0, attributeIndexSep);
+            }
+            String vStr = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+            Version v = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr);
+            Bundle existingBundle = findInstalled(sn, v);
+            if (existingBundle != null) {
+                LOGGER.debug("Found installed bundle: " + existingBundle);
+                return new BundleInstallerResult(existingBundle, false);
+            }
+            try {
+                is.reset();
+            } catch (IOException e) {
+                is.close();
+                is = new URL(bundleLocation).openStream();
+                // is = new BufferedInputStream(new
+                // URL(bundleLocation).openStream());
+            }
+            is = new BufferedInputStream(new FilterInputStream(is) {
+                @Override
+                public int read(byte b[], int off, int len) throws IOException {
+                    if (Thread.currentThread().isInterrupted()) {
+                        throw new InterruptedIOException();
+                    }
+                    return super.read(b, off, len);
+                }
+            });
+
+            LOGGER.debug("Installing bundle " + bundleLocation);
+            Bundle b = bundleContext.installBundle(bundleLocation, is);
+
+            if (startLevel > 0) {
+                b.adapt(BundleStartLevel.class).setStartLevel(startLevel);
+            }
+
+            return new BundleInstallerResult(b, true);
+        } finally {
+            is.close();
+        }
+    }
+
+    private Bundle findInstalled(String symbolicName, Version version) {
+        String vStr;
+        for (Bundle b : bundleContext.getBundles()) {
+            if (b.getSymbolicName() != null && b.getSymbolicName().equals(symbolicName)) {
+                vStr = (String) b.getHeaders().get(Constants.BUNDLE_VERSION);
+                Version bv = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr);
+                if (version.equals(bv)) {
+                    return b;
+                }
+            }
+        }
+        return null;
+    }
+
+    private InputStream getInputStreamForBundle(String bundleLocation) throws MalformedURLException, IOException {
+        InputStream is;
+        LOGGER.debug("Checking " + bundleLocation);
+        try {
+            int protocolIndex = bundleLocation.indexOf(":");
+            if (protocolIndex != -1) {
+                String protocol = bundleLocation.substring(0, protocolIndex);
+                waitForUrlHandler(protocol);
+            }
+            URL bundleUrl = new URL(bundleLocation);
+            is = new BufferedInputStream(bundleUrl.openStream());
+        } catch (RuntimeException e) {
+            LOGGER.error(e.getMessage());
+            throw e;
+        }
+        return is;
+    }
+
+    private void installToRegion(String region, Bundle b, boolean isNew) throws BundleException {
+        if (region != null && isNew) {
+            if (regionsPersistence != null) {
+                regionsPersistence.install(b, region);
+            } else {
+                throw new RuntimeException("Unable to find RegionsPersistence service, while installing " + region);
+            }
+        }
+    }
+
+    /**
+     * Will wait for the {@link URLStreamHandlerService} service for the
+     * specified protocol to be registered.
+     *
+     * @param protocol
+     */
+    private void waitForUrlHandler(String protocol) {
+        try {
+            Filter filter = bundleContext.createFilter("(&(" + Constants.OBJECTCLASS + "=" + URLStreamHandlerService.class.getName() + ")(url.handler.protocol=" + protocol + "))");
+            if (filter == null) {
+                return;
+            }
+            ServiceTracker<URLStreamHandlerService, URLStreamHandlerService> urlHandlerTracker = new ServiceTracker<URLStreamHandlerService, URLStreamHandlerService>(bundleContext, filter, null);
+            try {
+                urlHandlerTracker.open();
+                urlHandlerTracker.waitForService(30000);
+            } catch (InterruptedException e) {
+                LOGGER.debug("Interrupted while waiting for URL handler for protocol {}.", protocol);
+            } finally {
+                urlHandlerTracker.close();
+            }
+        } catch (Exception ex) {
+            LOGGER.error("Error creating service tracker.", ex);
+        }
+    }
+
+    protected Set<Bundle> findBundlesToRefresh(Set<Bundle> existing, Set<Bundle> installed) {
+        Set<Bundle> bundles = new HashSet<Bundle>();
+        bundles.addAll(findBundlesWithOptionalPackagesToRefresh(existing, installed));
+        bundles.addAll(findBundlesWithFragmentsToRefresh(existing, installed));
+        return bundles;
+    }
+
+    protected Set<Bundle> findBundlesWithFragmentsToRefresh(Set<Bundle> existing, Set<Bundle> installed) {
+        Set<Bundle> bundles = new HashSet<Bundle>();
+        Set<Bundle> oldBundles = new HashSet<Bundle>(existing);
+        oldBundles.removeAll(installed);
+        if (!oldBundles.isEmpty()) {
+            for (Bundle b : installed) {
+                String hostHeader = (String) b.getHeaders().get(Constants.FRAGMENT_HOST);
+                if (hostHeader != null) {
+                    Clause[] clauses = Parser.parseHeader(hostHeader);
+                    if (clauses != null && clauses.length > 0) {
+                        Clause path = clauses[0];
+                        for (Bundle hostBundle : oldBundles) {
+                            if (hostBundle.getSymbolicName().equals(path.getName())) {
+                                String ver = path.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE);
+                                if (ver != null) {
+                                    VersionRange v = VersionRange.parseVersionRange(ver);
+                                    if (v.contains(hostBundle.getVersion())) {
+                                        bundles.add(hostBundle);
+                                    }
+                                } else {
+                                    bundles.add(hostBundle);
+                                }
+                            }
                         }
+                    }
                 }
-                refreshPackages(null);
+            }
+        }
+        return bundles;
+    }
+
+    protected Set<Bundle> findBundlesWithOptionalPackagesToRefresh(Set<Bundle> existing, Set<Bundle> installed) {
+        // First pass: include all bundles contained in these features
+        Set<Bundle> bundles = new HashSet<Bundle>(existing);
+        bundles.removeAll(installed);
+        if (bundles.isEmpty()) {
+            return bundles;
+        }
+        // Second pass: for each bundle, check if there is any unresolved
+        // optional package that could be resolved
+        Map<Bundle, List<Clause>> imports = new HashMap<Bundle, List<Clause>>();
+        for (Iterator<Bundle> it = bundles.iterator(); it.hasNext(); ) {
+            Bundle b = it.next();
+            String importsStr = (String) b.getHeaders().get(
+                    Constants.IMPORT_PACKAGE);
+            List<Clause> importsList = getOptionalImports(importsStr);
+            if (importsList.isEmpty()) {
+                it.remove();
+            } else {
+                imports.put(b, importsList);
+            }
+        }
+        if (bundles.isEmpty()) {
+            return bundles;
+        }
+        // Third pass: compute a list of packages that are exported by our
+        // bundles and see if
+        // some exported packages can be wired to the optional imports
+        List<Clause> exports = new ArrayList<Clause>();
+        for (Bundle b : installed) {
+            String exportsStr = (String) b.getHeaders().get(
+                    Constants.EXPORT_PACKAGE);
+            if (exportsStr != null) {
+                Clause[] exportsList = Parser.parseHeader(exportsStr);
+                exports.addAll(Arrays.asList(exportsList));
+            }
         }
+        for (Iterator<Bundle> it = bundles.iterator(); it.hasNext(); ) {
+            Bundle b = it.next();
+            List<Clause> importsList = imports.get(b);
+            for (Iterator<Clause> itpi = importsList.iterator(); itpi.hasNext(); ) {
+                Clause pi = itpi.next();
+                boolean matching = false;
+                for (Clause pe : exports) {
+                    if (pi.getName().equals(pe.getName())) {
+                        String evStr = pe
+                                .getAttribute(Constants.VERSION_ATTRIBUTE);
+                        String ivStr = pi
+                                .getAttribute(Constants.VERSION_ATTRIBUTE);
+                        Version exported = evStr != null ? Version
+                                .parseVersion(evStr) : Version.emptyVersion;
+                        VersionRange imported = ivStr != null ? VersionRange
+                                .parseVersionRange(ivStr)
+                                : VersionRange.ANY_VERSION;
+                        if (imported.contains(exported)) {
+                            matching = true;
+                            break;
+                        }
+                    }
+                }
+                if (!matching) {
+                    itpi.remove();
+                }
+            }
+            if (importsList.isEmpty()) {
+                it.remove();
+            } else {
+                LOGGER.debug(
+                        "Refeshing bundle {} ({}) to solve the following optional imports",
+                        b.getSymbolicName(), b.getBundleId());
+                for (Clause p : importsList) {
+                    LOGGER.debug("    {}", p);
+                }
 
+            }
+        }
+        return bundles;
+    }
+
+    /*
+     * Get the list of optional imports from an OSGi Import-Package string
+     */
+    protected List<Clause> getOptionalImports(String importsStr) {
+        Clause[] imports = Parser.parseHeader(importsStr);
+        List<Clause> result = new LinkedList<Clause>();
+        for (Clause anImport : imports) {
+            String resolution = anImport
+                    .getDirective(Constants.RESOLUTION_DIRECTIVE);
+            if (Constants.RESOLUTION_OPTIONAL.equals(resolution)) {
+                result.add(anImport);
+            }
+        }
+        return result;
+    }
+
+    protected void refreshPackages(Collection<Bundle> bundles) {
+        final Object refreshLock = new Object();
+        FrameworkWiring wiring = bundleContext.getBundle().adapt(FrameworkWiring.class);
+        if (wiring != null) {
+            synchronized (refreshLock) {
+                wiring.refreshBundles(bundles, new FrameworkListener() {
+                    public void frameworkEvent(FrameworkEvent event) {
+                        if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
+                            synchronized (refreshLock) {
+                                refreshLock.notifyAll();
+                            }
+                        }
+                    }
+                });
+                try {
+                    refreshLock.wait(refreshTimeout);
+                } catch (InterruptedException e) {
+                    LOGGER.warn(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    public void uninstall(Set<Bundle> bundles) {
+        uninstall(bundles, true);
+    }
+
+    public void uninstall(Set<Bundle> bundles, boolean refresh) {
+        for (Bundle b : bundles) {
+            try {
+                b.uninstall();
+            } catch (Exception e2) {
+                // Ignore
+            }
+        }
+        if (refresh) {
+            refreshPackages(null);
+        }
+    }
+
+    public void uninstall(List<Bundle> bundles) {
+        uninstall(bundles, true);
+    }
+
+    public void uninstall(List<Bundle> bundles, boolean refresh) {
+        for (Bundle b : bundles) {
+            try {
+                b.uninstall();
+            } catch (Exception e2) {
+                // Ignore
+            }
+        }
+        if (refresh) {
+            refreshPackages(null);
+        }
+    }
+
+    public void uninstallById(Set<Long> bundles) throws BundleException, InterruptedException {
+        uninstallById(bundles, true);
+    }
+
+    public void uninstallById(Set<Long> bundles, boolean refresh) throws BundleException,
+            InterruptedException {
+        for (long bundleId : bundles) {
+            Bundle b = bundleContext.getBundle(bundleId);
+            if (b != null) {
+                b.uninstall();
+            }
+        }
+        if (refresh) {
+            refreshPackages(null);
+        }
+    }
+
+    public File getDataFile(String fileName) {
+        return bundleContext.getDataFile(fileName);
+    }
+
+    EventAdminListener createAndRegisterEventAdminListener() {
+        EventAdminListener listener = null;
+        try {
+            getClass().getClassLoader().loadClass(
+                    "org.bundles.service.event.EventAdmin");
+            listener = new EventAdminListener(bundleContext);
+        } catch (Throwable t) {
+            // Ignore, if the EventAdmin package is not available, just don't
+            // use it
+            LOGGER.debug("EventAdmin package is not available, just don't use it");
+        }
+        return listener;
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public ServiceTracker createServiceTrackerForResolverName(String resolver)
+            throws InvalidSyntaxException {
+        String filter = "(&(" + Constants.OBJECTCLASS + "="
+                + Resolver.class.getName() + ")(name=" + resolver + "))";
+        return new ServiceTracker(bundleContext,
+                FrameworkUtil.createFilter(filter), null);
+    }
+
+    public void refreshBundles(Set<Bundle> existing, Set<Bundle> installed,
+                               EnumSet<Option> options) {
+        boolean print = options.contains(Option.PrintBundlesToRefresh);
+        boolean refresh = !options.contains(Option.NoAutoRefreshBundles);
+        if (print || refresh) {
+            Set<Bundle> bundlesToRefresh = findBundlesToRefresh(existing,
+                    installed);
+            StringBuilder sb = new StringBuilder();
+            for (Bundle b : bundlesToRefresh) {
+                if (sb.length() > 0) {
+                    sb.append(", ");
+                }
+                sb.append(b.getSymbolicName()).append(" (")
+                        .append(b.getBundleId()).append(")");
+            }
+            LOGGER.debug("Bundles to refresh: {}", sb.toString());
+            if (!bundlesToRefresh.isEmpty()) {
+                if (print) {
+                    if (refresh) {
+                        System.out.println("Refreshing bundles "
+                                + sb.toString());
+                    } else {
+                        System.out
+                                .println("The following bundles may need to be refreshed: "
+                                        + sb.toString());
+                    }
+                }
+                if (refresh) {
+                    LOGGER.debug("Refreshing bundles: {}", sb.toString());
+                    refreshPackages(bundlesToRefresh);
+                }
+            }
+        }
+    }
+
+    public BundleContext getBundleContext() {
+        return this.bundleContext;
+    }
+
+    public static class BundleInstallerResult {
+        Bundle bundle;
+        boolean isNew;
+
+        public BundleInstallerResult(Bundle bundle, boolean isNew) {
+            super();
+            this.bundle = bundle;
+            this.isNew = isNew;
+        }
 
-	public void uninstallById(Set<Long> bundles) throws BundleException,
-			InterruptedException {
-		for (long bundleId : bundles) {
-			Bundle b = bundleContext.getBundle(bundleId);
-			if (b != null) {
-				b.uninstall();
-			}
-		}
-		refreshPackages(null);
-	}
-
-	public File getDataFile(String fileName) {
-		return bundleContext.getDataFile(fileName);
-	}
-
-	EventAdminListener createAndRegisterEventAdminListener() {
-		EventAdminListener listener = null;
-		try {
-			getClass().getClassLoader().loadClass(
-					"org.bundles.service.event.EventAdmin");
-			listener = new EventAdminListener(bundleContext);
-		} catch (Throwable t) {
-			// Ignore, if the EventAdmin package is not available, just don't
-			// use it
-			LOGGER.debug("EventAdmin package is not available, just don't use it");
-		}
-		return listener;
-	}
-
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	public ServiceTracker createServiceTrackerForResolverName(String resolver)
-			throws InvalidSyntaxException {
-		String filter = "(&(" + Constants.OBJECTCLASS + "="
-				+ Resolver.class.getName() + ")(name=" + resolver + "))";
-		return new ServiceTracker(bundleContext,
-				FrameworkUtil.createFilter(filter), null);
-	}
-
-	public void refreshBundles(Set<Bundle> existing, Set<Bundle> installed,
-			EnumSet<Option> options) {
-		boolean print = options.contains(Option.PrintBundlesToRefresh);
-		boolean refresh = !options.contains(Option.NoAutoRefreshBundles);
-		if (print || refresh) {
-			Set<Bundle> bundlesToRefresh = findBundlesToRefresh(existing,
-					installed);
-			StringBuilder sb = new StringBuilder();
-			for (Bundle b : bundlesToRefresh) {
-				if (sb.length() > 0) {
-					sb.append(", ");
-				}
-				sb.append(b.getSymbolicName()).append(" (")
-						.append(b.getBundleId()).append(")");
-			}
-			LOGGER.debug("Bundles to refresh: {}", sb.toString());
-			if (!bundlesToRefresh.isEmpty()) {
-				if (print) {
-					if (refresh) {
-						System.out.println("Refreshing bundles "
-								+ sb.toString());
-					} else {
-						System.out
-								.println("The following bundles may need to be refreshed: "
-										+ sb.toString());
-					}
-				}
-				if (refresh) {
-					LOGGER.debug("Refreshing bundles: {}", sb.toString());
-					refreshPackages(bundlesToRefresh);
-				}
-			}
-		}
-	}
-
-	public BundleContext getBundleContext() {
-	    return this.bundleContext;
-	}
-	
-	public static class BundleInstallerResult {
-		Bundle bundle;
-		boolean isNew;
-
-		public BundleInstallerResult(Bundle bundle, boolean isNew) {
-			super();
-			this.bundle = bundle;
-			this.isNew = isNew;
-		}
-
-	}
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/3c1b01d4/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
index 58b4ad0..be37db6 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
@@ -637,6 +637,10 @@ public class FeaturesServiceImpl implements FeaturesService {
     }
 
     public void uninstallFeature(String name) throws Exception {
+        uninstallFeature(name, EnumSet.noneOf(Option.class));
+    }
+
+    public void uninstallFeature(String name, EnumSet<Option> options) throws Exception {
         List<String> versions = new ArrayList<String>();
         for (Feature f : installed.keySet()) {
             if (name.equals(f.getName())) {
@@ -657,14 +661,22 @@ public class FeaturesServiceImpl implements FeaturesService {
             sb.append("). Please specify the version to uninstall.");
             throw new Exception(sb.toString());
         }
-        uninstallFeature(name, versions.get(0));
+        uninstallFeature(name, versions.get(0), options);
     }
-    
+
     public void uninstallFeature(String name, String version) throws Exception {
+        uninstallFeature(name, version, EnumSet.noneOf(Option.class));
+    }
+
+    public void uninstallFeature(String name, String version, EnumSet<Option> options) throws Exception {
     	Feature feature = getFeature(name, version);
         if (feature == null || !installed.containsKey(feature)) {
-            throw new Exception("Feature named '" + name 
-            		+ "' with version '" + version + "' is not installed");
+            throw new Exception("Feature named '" + name + "' with version '" + version + "' is not installed");
+        }
+        boolean verbose = options != null && options.contains(Option.Verbose);
+        boolean refresh = options == null || !options.contains(Option.NoAutoRefreshBundles);
+        if (verbose) {
+            System.out.println("Uninstalling feature " + feature.getName() + " " + feature.getVersion());
         }
         // Grab all the bundles installed by this feature
         // and remove all those who will still be in use.
@@ -702,7 +714,7 @@ public class FeaturesServiceImpl implements FeaturesService {
                 }
             });
         }
-        bundleManager.uninstall(bundlesDescendSortedByStartLvl);
+        bundleManager.uninstall(bundlesDescendSortedByStartLvl, refresh);
         callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureUninstalled, false));
         saveState();
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/3c1b01d4/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java b/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
index 4dc65a8..da4a36e 100644
--- a/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
+++ b/features/core/src/main/java/org/apache/karaf/features/management/FeaturesServiceMBean.java
@@ -46,8 +46,12 @@ public interface FeaturesServiceMBean {
 
     void uninstallFeature(String name) throws Exception;
 
+    void uninstallFeature(String name, boolean noRefresh) throws Exception;
+
     void uninstallFeature(String name, String version) throws Exception;
 
+    void uninstallFeature(String name, String version, boolean noRefresh) throws Exception;
+
     String FEATURE_NAME = "Name";
 
     String FEATURE_VERSION = "Version";

http://git-wip-us.apache.org/repos/asf/karaf/blob/3c1b01d4/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java b/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
index c4fe307..700e835 100644
--- a/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/management/internal/FeaturesServiceMBeanImpl.java
@@ -28,11 +28,7 @@ import javax.management.Notification;
 import javax.management.ObjectName;
 import javax.management.openmbean.TabularData;
 
-import org.apache.karaf.features.Feature;
-import org.apache.karaf.features.FeatureEvent;
-import org.apache.karaf.features.FeaturesListener;
-import org.apache.karaf.features.Repository;
-import org.apache.karaf.features.RepositoryEvent;
+import org.apache.karaf.features.*;
 import org.apache.karaf.features.management.FeaturesServiceMBean;
 import org.apache.karaf.features.management.codec.JmxFeature;
 import org.apache.karaf.features.management.codec.JmxFeatureEvent;
@@ -206,10 +202,26 @@ public class FeaturesServiceMBeanImpl extends StandardEmitterMBean implements
         featuresService.uninstallFeature(name);
     }
 
+    public void uninstallFeature(String name, boolean noRefresh) throws Exception {
+        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
+        if (noRefresh) {
+            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        featuresService.uninstallFeature(name, options);
+    }
+
     public void uninstallFeature(String name, String version) throws Exception {
         featuresService.uninstallFeature(name, version);
     }
 
+    public void uninstallFeature(String name, String version, boolean noRefresh) throws Exception {
+        EnumSet<org.apache.karaf.features.FeaturesService.Option> options = EnumSet.noneOf(org.apache.karaf.features.FeaturesService.Option.class);
+        if (noRefresh) {
+            options.add(org.apache.karaf.features.FeaturesService.Option.NoAutoRefreshBundles);
+        }
+        featuresService.uninstallFeature(name, version, options);
+    }
+
     public void setBundleContext(BundleContext bundleContext) {
         this.bundleContext = bundleContext;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/3c1b01d4/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java b/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
index a44e729..bce3735 100644
--- a/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/FeaturesServiceTest.java
@@ -51,6 +51,7 @@ import org.junit.Test;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
+import org.osgi.service.packageadmin.PackageAdmin;
 
 public class FeaturesServiceTest extends TestBase {
     private static final String FEATURE_WITH_INVALID_BUNDLE = "<features name='test' xmlns='http://karaf.apache.org/xmlns/features/v1.0.0'>"
@@ -143,7 +144,7 @@ public class FeaturesServiceTest extends TestBase {
         expect(bundleManager.installBundleIfNeeded("bundle-0.1", 0, null)).andReturn(new BundleInstallerResult(bundlef101, false));
         expect(bundleManager.getBundleContext()).andReturn(bundleContext);
         ignoreRefreshes(bundleManager);
-        bundleManager.uninstall(Collections.EMPTY_LIST);        
+        bundleManager.uninstall(Collections.EMPTY_LIST, true);
         EasyMock.expectLastCall().times(2);
         
         
@@ -215,7 +216,7 @@ public class FeaturesServiceTest extends TestBase {
         expect(bundleManager.getBundleContext()).andReturn(bundleContext).anyTimes();
         expect(bundleContext.getBundle(12345)).andReturn(bundlef101).anyTimes();
         ignoreRefreshes(bundleManager);
-        bundleManager.uninstall(Collections.EMPTY_LIST);
+        bundleManager.uninstall(Collections.EMPTY_LIST, true);
        
         EasyMock.expectLastCall().anyTimes();
         replay(bundleManager);
@@ -237,7 +238,7 @@ public class FeaturesServiceTest extends TestBase {
         expect(bundleManager.installBundleIfNeeded(bundleUri, 0, null)).andReturn(new BundleInstallerResult(installedBundle, true));
         expect(bundleManager.getBundleContext()).andReturn(bundleContext);
         ignoreRefreshes(bundleManager);
-        bundleManager.uninstall(Collections.EMPTY_LIST);
+        bundleManager.uninstall(Collections.EMPTY_LIST, true);
         EasyMock.expectLastCall().times(2);
         return bundleManager;
     }
@@ -296,9 +297,9 @@ public class FeaturesServiceTest extends TestBase {
         expect(bundleManager.installBundleIfNeeded("bundle-0.2", 0, null)).andReturn(new BundleInstallerResult(bundleVer02, true));
         expect(bundleManager.getBundleContext()).andReturn(bundleContext);
         ignoreRefreshes(bundleManager);
-        bundleManager.uninstall(Collections.EMPTY_LIST);        
+        bundleManager.uninstall(Collections.EMPTY_LIST, true);
+
         EasyMock.expectLastCall().times(2);
-        
 
         replay(bundleManager);
         FeaturesServiceImpl svc = new FeaturesServiceImpl(bundleManager);
@@ -329,7 +330,7 @@ public class FeaturesServiceTest extends TestBase {
         expect(bundleManager.installBundleIfNeeded(bundleVer01Uri, 0, null)).andReturn(new BundleInstallerResult(bundleVer01, false));
         expect(bundleManager.getBundleContext()).andReturn(bundleContext);
         ignoreRefreshes(bundleManager);
-        bundleManager.uninstall(Collections.EMPTY_LIST);        
+        bundleManager.uninstall(Collections.EMPTY_LIST, true);
         
         EasyMock.expectLastCall().times(2);
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/3c1b01d4/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
index a2ee978..94c153a 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/features/InstallKarsMojo.java
@@ -587,10 +587,18 @@ public class InstallKarsMojo extends MojoSupport {
         }
 
         @Override
+        public void uninstallFeature(String name, EnumSet<Option> options) {
+        }
+
+        @Override
         public void uninstallFeature(String name, String version) throws Exception {
         }
 
         @Override
+        public void uninstallFeature(String name, String version, EnumSet<Option> options) {
+        }
+
+        @Override
         public Feature[] listFeatures() throws Exception {
             return new Feature[0];
         }