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

[4/7] [KARAF-2888] Extract the deployment for easier unit testing

http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
index d902a0e..099d175 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeaturesServiceImpl.java
@@ -25,17 +25,13 @@ import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
@@ -45,21 +41,14 @@ import java.util.concurrent.Executors;
 
 import org.apache.felix.utils.version.VersionRange;
 import org.apache.felix.utils.version.VersionTable;
-import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeatureEvent;
 import org.apache.karaf.features.FeaturesListener;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.RepositoryEvent;
-import org.apache.karaf.features.internal.download.StreamProvider;
-import org.apache.karaf.features.internal.region.SubsystemResolver;
-import org.apache.karaf.features.internal.util.ChecksumUtils;
 import org.apache.karaf.features.internal.util.JsonReader;
 import org.apache.karaf.features.internal.util.JsonWriter;
-import org.apache.karaf.features.internal.util.Macro;
-import org.apache.karaf.features.internal.util.MapUtils;
-import org.apache.karaf.features.internal.util.MultiException;
 import org.apache.karaf.util.collections.CopyOnWriteArrayIdentityList;
 import org.eclipse.equinox.region.Region;
 import org.eclipse.equinox.region.RegionDigraph;
@@ -67,63 +56,26 @@ import org.eclipse.equinox.region.RegionFilterBuilder;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
 import org.osgi.framework.FrameworkEvent;
 import org.osgi.framework.FrameworkListener;
-import org.osgi.framework.ServiceReference;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.Version;
 import org.osgi.framework.startlevel.BundleStartLevel;
 import org.osgi.framework.startlevel.FrameworkStartLevel;
-import org.osgi.framework.wiring.BundleRevision;
-import org.osgi.framework.wiring.BundleWire;
-import org.osgi.framework.wiring.BundleWiring;
 import org.osgi.framework.wiring.FrameworkWiring;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Resource;
-import org.osgi.resource.Wire;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.apache.felix.resolver.Util.getSymbolicName;
-import static org.apache.felix.resolver.Util.getVersion;
-import static org.apache.karaf.features.internal.resolver.ResourceUtils.TYPE_SUBSYSTEM;
-import static org.apache.karaf.features.internal.resolver.ResourceUtils.getFeatureId;
-import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
 import static org.apache.karaf.features.internal.service.StateStorage.toStringStringSetMap;
 import static org.apache.karaf.features.internal.util.MapUtils.addToMapSet;
-import static org.apache.karaf.features.internal.util.MapUtils.apply;
 import static org.apache.karaf.features.internal.util.MapUtils.copy;
-import static org.apache.karaf.features.internal.util.MapUtils.diff;
-import static org.apache.karaf.features.internal.util.MapUtils.flatten;
-import static org.apache.karaf.features.internal.util.MapUtils.map;
-import static org.apache.karaf.features.internal.util.MapUtils.removeFromMapSet;
-import static org.osgi.framework.Bundle.ACTIVE;
-import static org.osgi.framework.Bundle.RESOLVED;
-import static org.osgi.framework.Bundle.STARTING;
-import static org.osgi.framework.Bundle.STOPPING;
-import static org.osgi.framework.Bundle.STOP_TRANSIENT;
-import static org.osgi.framework.Bundle.UNINSTALLED;
-import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
-import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
-import static org.osgi.resource.Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE;
-import static org.osgi.resource.Namespace.EFFECTIVE_ACTIVE;
 
 /**
  *
  */
-public class FeaturesServiceImpl implements FeaturesService {
-
-    public static final String UPDATE_SNAPSHOTS_NONE = "none";
-    public static final String UPDATE_SNAPSHOTS_CRC = "crc";
-    public static final String UPDATE_SNAPSHOTS_ALWAYS = "always";
-    public static final String DEFAULT_UPDATE_SNAPSHOTS = UPDATE_SNAPSHOTS_CRC;
-
-    public static final String DEFAULT_FEATURE_RESOLUTION_RANGE = "${range;[====,====]}";
-    public static final String DEFAULT_BUNDLE_UPDATE_RANGE = "${range;[==,=+)}";
+public class FeaturesServiceImpl implements FeaturesService, Deployer.DeployCallback {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(FeaturesServiceImpl.class);
-    private static final String SNAPSHOT = "SNAPSHOT";
-    private static final String MAVEN = "mvn:";
 
     /**
      * Our bundle.
@@ -149,13 +101,13 @@ public class FeaturesServiceImpl implements FeaturesService {
     private final String overrides;
     /**
      * Range to use when a version is specified on a feature dependency.
-     * The default is {@link FeaturesServiceImpl#DEFAULT_FEATURE_RESOLUTION_RANGE}
+     * The default is {@link org.apache.karaf.features.FeaturesService#DEFAULT_FEATURE_RESOLUTION_RANGE}
      */
     private final String featureResolutionRange;
     /**
      * Range to use when verifying if a bundle should be updated or
      * new bundle installed.
-     * The default is {@link FeaturesServiceImpl#DEFAULT_BUNDLE_UPDATE_RANGE}
+     * The default is {@link org.apache.karaf.features.FeaturesService#DEFAULT_BUNDLE_UPDATE_RANGE}
      */
     private final String bundleUpdateRange;
     /**
@@ -331,7 +283,7 @@ public class FeaturesServiceImpl implements FeaturesService {
         listeners.remove(listener);
     }
 
-    protected void callListeners(FeatureEvent event) {
+    public void callListeners(FeatureEvent event) {
         if (eventAdminListener != null) {
             eventAdminListener.featureEvent(event);
         }
@@ -885,36 +837,37 @@ public class FeaturesServiceImpl implements FeaturesService {
         }
     }
 
-    static class DeploymentState {
-        Map<Long, Bundle> bundles;
-        Map<String, Feature> features;
-        Map<String, Set<Long>> bundlesPerRegion;
-        Map<String, Map<String, Map<String, Set<String>>>> filtersPerRegion;
-    }
-
-    protected DeploymentState getDeploymentState() throws Exception {
-        DeploymentState state = new DeploymentState();
+    protected Deployer.DeploymentState getDeploymentState(State state) throws Exception {
+        Deployer.DeploymentState dstate = new Deployer.DeploymentState();
+        // State
+        dstate.state = state;
+        // Service bundle
+        dstate.serviceBundle = bundle;
+        // Start level
+        FrameworkStartLevel fsl = systemBundleContext.getBundle().adapt(FrameworkStartLevel.class);
+        dstate.initialBundleStartLevel = fsl.getInitialBundleStartLevel();
+        dstate.currentStartLevel = fsl.getStartLevel();
         // Bundles
-        state.bundles = new HashMap<>();
+        dstate.bundles = new HashMap<>();
         for (Bundle bundle : systemBundleContext.getBundles()) {
-            state.bundles.put(bundle.getBundleId(), bundle);
+            dstate.bundles.put(bundle.getBundleId(), bundle);
         }
         // Features
-        state.features = new HashMap<>();
+        dstate.features = new HashMap<>();
         for (Map<String, Feature> m : getFeatures().values()) {
             for (Feature feature : m.values()) {
                 String id = feature.getName() + "/" + VersionTable.getVersion(feature.getVersion());
-                state.features.put(id, feature);
+                dstate.features.put(id, feature);
             }
         }
         // Region -> bundles mapping
         // Region -> policy mapping
-        state.bundlesPerRegion = new HashMap<>();
-        state.filtersPerRegion = new HashMap<>();
+        dstate.bundlesPerRegion = new HashMap<>();
+        dstate.filtersPerRegion = new HashMap<>();
         RegionDigraph clone = digraph.copy();
         for (Region region : clone.getRegions()) {
             // Get bundles
-            state.bundlesPerRegion.put(region.getName(), new HashSet<>(region.getBundleIds()));
+            dstate.bundlesPerRegion.put(region.getName(), new HashSet<>(region.getBundleIds()));
             // Get policies
             Map<String, Map<String, Set<String>>> edges = new HashMap<>();
             for (RegionDigraph.FilteredRegion fr : clone.getEdges(region)) {
@@ -927,1030 +880,150 @@ public class FeaturesServiceImpl implements FeaturesService {
                 }
                 edges.put(fr.getRegion().getName(), policy);
             }
-            state.filtersPerRegion.put(region.getName(), edges);
+            dstate.filtersPerRegion.put(region.getName(), edges);
         }
         // Return
-        return state;
+        return dstate;
     }
 
+    private Deployer.DeploymentRequest getDeploymentRequest(Map<String, Set<String>> requestedFeatures, Map<String, Map<String, RequestedState>> stateChanges, EnumSet<Option> options) {
+        Deployer.DeploymentRequest request = new Deployer.DeploymentRequest();
+        request.bundleUpdateRange = bundleUpdateRange;
+        request.featureResolutionRange = featureResolutionRange;
+        request.globalRepository = globalRepository;
+        request.overrides = overrides;
+        request.requestedFeatures = requestedFeatures;
+        request.stateChanges = stateChanges;
+        request.options = options;
+        return request;
+    }
+
+
+
     public void doInstallFeatures(Map<String, Set<String>> requestedFeatures,            // all request features
                                   Map<String, Map<String, RequestedState>> stateChanges, // features state changes
                                   State state,                                           // current state
                                   EnumSet<Option> options                                // installation options
     ) throws Exception {
 
-        boolean noRefreshUnmanaged = options.contains(Option.NoAutoRefreshUnmanagedBundles);
-        boolean noRefreshManaged = options.contains(Option.NoAutoRefreshManagedBundles);
-        boolean noRefresh = options.contains(Option.NoAutoRefreshBundles);
-        boolean noStart = options.contains(Option.NoAutoStartBundles);
-        boolean verbose = options.contains(Option.Verbose);
-        boolean simulate = options.contains(Option.Simulate);
-        boolean noManageBundles = options.contains(Option.NoAutoManageBundles);
-
-        // TODO: add an option to unmanage bundles instead of uninstalling those
-
-        DeploymentState dstate = getDeploymentState();
-
-        Map<String, Set<Long>> managedBundles = copy(state.managedBundles);
-
-        Map<String, Set<Bundle>> unmanagedBundles = apply(diff(dstate.bundlesPerRegion, state.managedBundles),
-                map(dstate.bundles));
-
-        // Resolve
-        // TODO: requirements
-        // TODO: bundles
-
-        SubsystemResolver resolver = new SubsystemResolver();
-        resolver.resolve(
-                dstate.features.values(),
-                requestedFeatures,
-                apply(unmanagedBundles, adapt(BundleRevision.class)),
-                Overrides.loadOverrides(this.overrides),
-                featureResolutionRange,
-                globalRepository);
-
-        Map<String, StreamProvider> providers = resolver.getProviders();
-        Map<String, Set<Resource>> featuresPerRegion = resolver.getFeaturesPerRegions();
-        Map<String, Set<String>> installedFeatures = apply(featuresPerRegion, featureId());
-        Map<String, Set<String>> newFeatures = diff(installedFeatures, state.installedFeatures);
-        Map<String, Set<String>> delFeatures = diff(state.installedFeatures, installedFeatures);
-
-        //
-        // Compute requested features state
-        //
-        Map<String, Map<String, String>> stateFeatures = copy(state.stateFeatures);
-        for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
-            Map<String, String> map = stateFeatures.get(entry.getKey());
-            if (map != null) {
-                map.entrySet().removeAll(entry.getValue());
-                if (map.isEmpty()) {
-                    stateFeatures.remove(entry.getKey());
-                }
-            }
-        }
-        for (Map.Entry<String, Map<String, RequestedState>> entry1 : stateChanges.entrySet()) {
-            String region = entry1.getKey();
-            Map<String, String> regionStates = stateFeatures.get(region);
-            if (regionStates != null) {
-                for (Map.Entry<String, RequestedState> entry2 : entry1.getValue().entrySet()) {
-                    String feature = entry2.getKey();
-                    if (regionStates.containsKey(feature)) {
-                        regionStates.put(feature, entry2.getValue().name());
-                    }
-                }
-            }
-        }
-        for (Map.Entry<String, Set<String>> entry : newFeatures.entrySet()) {
-            for (String feature : entry.getValue()) {
-                Map<String, String> map = stateFeatures.get(entry.getKey());
-                if (map == null) {
-                    map = new HashMap<>();
-                    stateFeatures.put(entry.getKey(), map);
-                }
-                map.put(feature, noStart ? RequestedState.Installed.name() : RequestedState.Started.name());
-            }
-        }
-
-        // Compute information for each bundle
-        Map<String, Map<String, BundleInfo>> bundleInfos = resolver.getBundleInfos();
-
-        //
-        // Compute deployment
-        //
-        Deployment deployment = computeDeployment(dstate, resolver, state);
-
-        //
-        // Compute the set of bundles to refresh
-        //
-        Set<Bundle> toRefresh = new TreeSet<>(new BundleComparator()); // sort is only used for display
-        for (RegionDeployment regionDeployment : deployment.regions.values()) {
-            toRefresh.addAll(regionDeployment.toDelete);
-            toRefresh.addAll(regionDeployment.toUpdate.keySet());
-        }
-        if (!noRefreshManaged) {
-            computeBundlesToRefresh(toRefresh, dstate.bundles.values(), deployment.resToBnd, resolver.getWiring());
-        }
-        if (noRefreshUnmanaged) {
-            toRefresh.removeAll(flatten(unmanagedBundles));
-        }
-
-        // Automatically turn unmanaged bundles into managed bundles
-        // if they are required by a feature and no other unmanaged
-        // bundles have a requirement on it
-        Set<Bundle> toManage = new TreeSet<>(new BundleComparator()); // sort is only used for display
-        if (!noManageBundles) {
-            Set<Resource> features = resolver.getFeatures().keySet();
-            Set<? extends Resource> unmanaged = apply(flatten(unmanagedBundles), adapt(BundleRevision.class));
-            Set<Resource> requested = new HashSet<>();
-            // Gather bundles required by a feature
-            for (List<Wire> wires : resolver.getWiring().values()) {
-                for (Wire wire : wires) {
-                    if (features.contains(wire.getRequirer()) && unmanaged.contains(wire.getProvider())) {
-                        requested.add(wire.getProvider());
-                    }
-                }
-            }
-            // Now, we know which bundles are completely unmanaged
-            unmanaged.removeAll(requested);
-            // Check if bundles have wires from really unmanaged bundles
-            for (List<Wire> wires : resolver.getWiring().values()) {
-                for (Wire wire : wires) {
-                    if (requested.contains(wire.getProvider()) && unmanaged.contains(wire.getRequirer())) {
-                        requested.remove(wire.getProvider());
-                    }
-                }
-            }
-            if (!requested.isEmpty()) {
-                Map<Long, String> bundleToRegion = new HashMap<>();
-                for (Map.Entry<String, Set<Long>> entry : dstate.bundlesPerRegion.entrySet()) {
-                    for (long id : entry.getValue()) {
-                        bundleToRegion.put(id, entry.getKey());
-                    }
-                }
-                for (Resource rev : requested) {
-                    Bundle bundle = ((BundleRevision) rev).getBundle();
-                    long id = bundle.getBundleId();
-                    addToMapSet(managedBundles, bundleToRegion.get(id), id);
-                    toManage.add(bundle);
-                }
-            }
-        }
-
-        //
-        // Log deployment
-        //
-        logDeployment(deployment, verbose);
-
-        if (simulate) {
-            if (!noRefresh && !toRefresh.isEmpty()) {
-                print("  Bundles to refresh:", verbose);
-                for (Bundle bundle : toRefresh) {
-                    print("    " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
-                }
-            }
-            if (!toManage.isEmpty()) {
-                print("  Managing bundle:", verbose);
-                for (Bundle bundle : toManage) {
-                    print("    " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
-                }
-            }
-            return;
-        }
-
-        Set<Bundle> toStart = new HashSet<>();
-        Set<Bundle> toResolve = new HashSet<>();
-        Set<Bundle> toStop = new HashSet<>();
-
-        //
-        // Execute deployment
-        //
-        // #1: stop bundles that needs to be updated or uninstalled in order
-        // #2: uninstall needed bundles
-        // #3: update regions
-        // #4: update bundles
-        // #5: install bundles
-        // #6: save state
-        // #7: install configuration
-        // #8: refresh bundles
-        // #9: start bundles in order
-        // #10: send events
-        //
-
-        //
-        // Compute bundle states
-        //
-        Map<Resource, RequestedState> states = new HashMap<>();
-        for (Map.Entry<String, Set<Resource>> entry : resolver.getFeaturesPerRegions().entrySet()) {
-            String region = entry.getKey();
-            Map<String, String> fss = stateFeatures.get(region);
-            for (Resource feature : entry.getValue()) {
-                String fs = fss.get(getFeatureId(feature));
-                propagateState(states, feature, RequestedState.valueOf(fs), resolver);
-            }
-        }
-        states.keySet().retainAll(resolver.getBundles().keySet());
-        //
-        // Compute bundles to start, stop and resolve
-        //
-        for (Map.Entry<Resource, RequestedState> entry : states.entrySet()) {
-            Bundle bundle = deployment.resToBnd.get(entry.getKey());
-            if (bundle != null) {
-                switch (entry.getValue()) {
-                case Started:
-                    toResolve.add(bundle);
-                    toStart.add(bundle);
-                    break;
-                case Resolved:
-                    toResolve.add(bundle);
-                    toStop.add(bundle);
-                    break;
-                }
-            }
-        }
-        //
-        // Compute bundle all start levels and start levels to update
-        //
-        FrameworkStartLevel fsl = systemBundleContext.getBundle().adapt(FrameworkStartLevel.class);
-        int initialBundleStartLevel = fsl.getInitialBundleStartLevel();
-        int currentStartLevel = fsl.getStartLevel();
-        Map<Resource, Integer> startLevels = new HashMap<>();
-        Map<Bundle, Integer> toUpdateStartLevel = new HashMap<>();
-        for (Map.Entry<String, Set<Resource>> entry : resolver.getBundlesPerRegions().entrySet()) {
-            String region = entry.getKey();
-            for (Resource resource : entry.getValue()) {
-                BundleInfo bi = bundleInfos.get(region).get(getUri(resource));
-                if (bi != null) {
-                    int sl = bi.getStartLevel() > 0 ? bi.getStartLevel() : initialBundleStartLevel;
-                    startLevels.put(resource, sl);
-                    Bundle bundle = deployment.resToBnd.get(resource);
-                    if (bundle != null) {
-                        int curSl = bundle.adapt(BundleStartLevel.class).getStartLevel();
-                        if (sl != curSl) {
-                            toUpdateStartLevel.put(bundle, sl);
-                            if (sl > currentStartLevel) {
-                                toStop.add(bundle);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-
-        //
-        // Handle updates on the FeaturesService bundle
-        //
-        RegionDeployment rootRegionDeployment = deployment.regions.get(ROOT_REGION);
-        // We don't support uninstalling the bundle
-        if (rootRegionDeployment != null && rootRegionDeployment.toDelete.contains(bundle)) {
-            throw new UnsupportedOperationException("Uninstalling the FeaturesService bundle is not supported");
-        }
-        // If the bundle needs to be updated, do the following:
-        //  - create flag files to indicate the resolution must be continued after restart
-        //  - update the checksum and save the state
-        //  - compute bundles wired to the FeaturesService bundle that will be refreshed
-        //  - stop the bundle
-        //  - update the bundle
-        //  - refresh wired bundles
-        //  - start the bundle
-        //  - exit
-        // When restarting, the resolution will be attempted again
-        if (rootRegionDeployment != null && rootRegionDeployment.toUpdate.containsKey(bundle)) {
-            writeResolve(requestedFeatures, options);
-            // If the bundle is updated because of a different checksum,
-            // save the new checksum persistently
-            if (deployment.bundleChecksums.containsKey(bundle.getBundleId())) {
-                synchronized (lock) {
-                    this.state.bundleChecksums.put(bundle.getBundleId(), deployment.bundleChecksums.get(bundle.getBundleId()));
-                    saveState();
-                }
-            }
-            Resource resource = rootRegionDeployment.toUpdate.get(bundle);
-            String uri = getUri(resource);
-            print("The FeaturesService bundle needs is being updated with " + uri, verbose);
-            toRefresh.clear();
-            toRefresh.add(bundle);
-            computeBundlesToRefresh(toRefresh,
-                    dstate.bundles.values(),
-                    Collections.<Resource, Bundle>emptyMap(),
-                    Collections.<Resource, List<Wire>>emptyMap());
-            bundle.stop(STOP_TRANSIENT);
-            try (
-                    InputStream is = getBundleInputStream(resource, providers)
-            ) {
-                bundle.update(is);
-            }
-            refreshPackages(toRefresh);
-            bundle.start();
-            return;
-        }
-
-        //
-        // Perform bundle operations
-        //
-
-        //
-        // Stop bundles by chunks
-        //
-        for (RegionDeployment regionDeployment : deployment.regions.values()) {
-            toStop.addAll(regionDeployment.toUpdate.keySet());
-            toStop.addAll(regionDeployment.toDelete);
-        }
-        removeFragmentsAndBundlesInState(toStop, UNINSTALLED | RESOLVED | STOPPING);
-        if (!toStop.isEmpty()) {
-            print("Stopping bundles:", verbose);
-            while (!toStop.isEmpty()) {
-                List<Bundle> bs = getBundlesToStop(toStop);
-                for (Bundle bundle : bs) {
-                    print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
-                    // If the bundle start level will be changed, stop it persistently to
-                    // avoid a restart when the start level is actually changed
-                    bundle.stop(toUpdateStartLevel.containsKey(bundle) ? 0 : STOP_TRANSIENT);
-                    toStop.remove(bundle);
-                }
-            }
-        }
-
-        //
-        // Delete bundles
-        //
-        boolean hasToDelete = false;
-        for (RegionDeployment regionDeployment : deployment.regions.values()) {
-            if (hasToDelete = !regionDeployment.toDelete.isEmpty()) {
-                break;
-            }
-        }
-        if (hasToDelete) {
-            print("Uninstalling bundles:", verbose);
-            for (Map.Entry<String, RegionDeployment> entry : deployment.regions.entrySet()) {
-                String name = entry.getKey();
-                RegionDeployment regionDeployment = entry.getValue();
-                for (Bundle bundle : regionDeployment.toDelete) {
-                    print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
-                    bundle.uninstall();
-                    removeFromMapSet(managedBundles, name, bundle.getBundleId());
-                }
-            }
-        }
+        Deployer.DeploymentState dstate = getDeploymentState(state);
+        Deployer.DeploymentRequest request = getDeploymentRequest(requestedFeatures, stateChanges, options);
+        new Deployer(this).deploy(dstate, request);
+    }
 
-        //
-        // Update regions
-        //
-        {
-            RegionDigraph clone = digraph.copy();
-            RegionDigraph computedDigraph = resolver.getFlatDigraph();
-            Map<String, Map<String, Map<String, Set<String>>>> policies = copy(dstate.filtersPerRegion);
-            // Iterate through previously managed regions and
-            // delete those that do not contain any bundles anymore
-            for (String name : state.managedBundles.keySet()) {
-                if (!managedBundles.containsKey(name) && !unmanagedBundles.containsKey(name)) {
-                    dstate.filtersPerRegion.remove(name);
-                }
-            }
-            // Fix broken filters
-            for (String name : policies.keySet()) {
-                policies.get(name).keySet().retainAll(policies.keySet());
-            }
-            // Update managed regions
-            for (Region computedRegion : computedDigraph.getRegions()) {
-                String name = computedRegion.getName();
-                Map<String, Map<String, Set<String>>> policy = policies.get(name);
-                if (policy == null) {
-                    policy = new HashMap<>();
-                    policies.put(name, policy);
-                }
-                for (RegionDigraph.FilteredRegion fr : computedDigraph.getEdges(computedRegion)) {
-                    String r2 = fr.getRegion().getName();
-                    Map<String, Set<String>> filters = new HashMap<>();
-                    Map<String, Collection<String>> current = fr.getFilter().getSharingPolicy();
-                    for (String ns : current.keySet()) {
-                        for (String f : current.get(ns)) {
-                            addToMapSet(filters, ns, f);
-                        }
-                    }
-                    policy.put(r2, filters);
-                }
-            }
-            // Apply all changes
-            for (Region region : clone.getRegions()) {
-                clone.removeRegion(region);
-            }
-            for (String name : policies.keySet()) {
-                clone.createRegion(name);
-            }
-            for (String r1Name : policies.keySet()) {
-                Region r1 = clone.getRegion(r1Name);
-                Map<String, Map<String, Set<String>>> policy = policies.get(r1Name);
-                for (String r2Name : policy.keySet()) {
-                    Region r2 = clone.getRegion(r2Name);
-                    RegionFilterBuilder rfb = clone.createRegionFilterBuilder();
-                    for (String ns : policy.get(r2Name).keySet()) {
-                        for (String f : policy.get(r2Name).get(ns)) {
-                            rfb.allow(ns, f);
-                        }
-                    }
-                    clone.connect(r1, rfb.build(), r2);
-                }
-                // Dispatch bundles
-                if (unmanagedBundles.containsKey(r1Name)) {
-                    for (Bundle bundle : unmanagedBundles.get(r1Name)) {
-                        r1.addBundle(bundle);
-                    }
-                }
-                if (managedBundles.containsKey(r1Name)) {
-                    for (long id : managedBundles.get(r1Name)) {
-                        r1.addBundle(id);
-                    }
-                }
-            }
-            this.digraph.replace(clone);
+    public void print(String message, boolean verbose) {
+        LOGGER.info(message);
+        if (verbose) {
+            System.out.println(message);
         }
+    }
 
-
-        //
-        // Update bundles
-        //
-        boolean hasToUpdate = false;
-        for (RegionDeployment regionDeployment : deployment.regions.values()) {
-            if (hasToUpdate = !regionDeployment.toUpdate.isEmpty()) {
-                break;
-            }
-        }
-        if (hasToUpdate) {
-            print("Updating bundles:", verbose);
-            for (Map.Entry<String, RegionDeployment> rde : deployment.regions.entrySet()) {
-                for (Map.Entry<Bundle, Resource> entry : rde.getValue().toUpdate.entrySet()) {
-                    Bundle bundle = entry.getKey();
-                    Resource resource = entry.getValue();
-                    String uri = getUri(resource);
-                    print("  " + uri, verbose);
-                    try (
-                            InputStream is = getBundleInputStream(resource, providers)
-                    ) {
-                        bundle.update(is);
-                    }
-                    toStart.add(bundle);
-                }
-            }
-        }
-        //
-        // Update start levels
-        //
-        for (Map.Entry<Bundle, Integer> entry : toUpdateStartLevel.entrySet()) {
-            Bundle bundle = entry.getKey();
-            int sl = entry.getValue();
-            bundle.adapt(BundleStartLevel.class).setStartLevel(sl);
-        }
-        //
-        // Install bundles
-        //
-        boolean hasToInstall = false;
-        for (RegionDeployment regionDeployment : deployment.regions.values()) {
-            if (hasToInstall = !regionDeployment.toInstall.isEmpty()) {
-                break;
-            }
-        }
-        if (hasToInstall) {
-            print("Installing bundles:", verbose);
-            for (Map.Entry<String, RegionDeployment> entry : deployment.regions.entrySet()) {
-                String name = entry.getKey();
-                Region region = digraph.getRegion(name);
-                RegionDeployment regionDeployment = entry.getValue();
-                for (Resource resource : regionDeployment.toInstall) {
-                    String uri = getUri(resource);
-                    print("  " + uri, verbose);
-                    Bundle bundle;
-                    long crc;
-                    try (
-                            ChecksumUtils.CRCInputStream is = new ChecksumUtils.CRCInputStream(getBundleInputStream(resource, providers))
-                    ) {
-                        if (ROOT_REGION.equals(name)) {
-                            bundle = region.installBundleAtLocation(uri, is);
-                        } else {
-                            bundle = region.installBundle(uri, is);
-                        }
-                        crc = is.getCRC();
-                    }
-                    addToMapSet(managedBundles, name, bundle.getBundleId());
-                    deployment.resToBnd.put(resource, bundle);
-                    // save a checksum of installed snapshot bundle
-                    if (UPDATE_SNAPSHOTS_CRC.equals(updateSnaphots)
-                            && isUpdateable(resource) && !deployment.bundleChecksums.containsKey(bundle.getBundleId())) {
-                        deployment.bundleChecksums.put(bundle.getBundleId(), crc);
-                    }
-                    int startLevel = startLevels.get(resource);
-                    bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
-                    RequestedState reqState = states.get(resource);
-                    switch (reqState) {
-                    case Started:
-                        toResolve.add(bundle);
-                        toStart.add(bundle);
-                        break;
-                    case Resolved:
-                        toResolve.add(bundle);
-                        break;
-                    }
+    public void refreshPackages(Collection<Bundle> bundles) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        FrameworkWiring fw = systemBundleContext.getBundle().adapt(FrameworkWiring.class);
+        fw.refreshBundles(bundles, new FrameworkListener() {
+            @Override
+            public void frameworkEvent(FrameworkEvent event) {
+                if (event.getType() == FrameworkEvent.ERROR) {
+                    LOGGER.error("Framework error", event.getThrowable());
                 }
+                latch.countDown();
             }
-        }
+        });
+        latch.await();
+    }
 
-        //
-        // Update and save state
-        //
+    @Override
+    public void saveState(State state) {
         synchronized (lock) {
-            this.state.bundleChecksums.clear();
-            this.state.bundleChecksums.putAll(deployment.bundleChecksums);
-            this.state.requestedFeatures.clear();
-            this.state.requestedFeatures.putAll(requestedFeatures);
-            this.state.installedFeatures.clear();
-            this.state.installedFeatures.putAll(installedFeatures);
-            this.state.stateFeatures.clear();
-            this.state.stateFeatures.putAll(stateFeatures);
-            this.state.managedBundles.clear();
-            this.state.managedBundles.putAll(managedBundles);
+            this.state.replace(state);
             saveState();
         }
-
-        //
-        // Install configurations
-        //
-        if (configInstaller != null && !newFeatures.isEmpty()) {
-            Set<Feature> set = apply(flatten(newFeatures), map(dstate.features));
-            for (Feature feature : set) {
-                configInstaller.installFeatureConfigs(feature);
-            }
-        }
-
-        // TODO: remove this hack, but it avoids loading the class after the bundle is refreshed
-        new CopyOnWriteArrayIdentityList().iterator();
-        RequirementSort.sort(Collections.<Resource>emptyList());
-
-        if (!noRefresh) {
-            toStop = new HashSet<>();
-            toStop.addAll(toRefresh);
-            removeFragmentsAndBundlesInState(toStop, UNINSTALLED | RESOLVED | STOPPING);
-            if (!toStop.isEmpty()) {
-                print("Stopping bundles:", verbose);
-                while (!toStop.isEmpty()) {
-                    List<Bundle> bs = getBundlesToStop(toStop);
-                    for (Bundle bundle : bs) {
-                        print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
-                        bundle.stop(STOP_TRANSIENT);
-                        toStop.remove(bundle);
-                        toStart.add(bundle);
-                    }
-                }
-            }
-
-            if (!toRefresh.isEmpty()) {
-                print("Refreshing bundles:", verbose);
-                for (Bundle bundle : toRefresh) {
-                    print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
-                }
-                if (!toRefresh.isEmpty()) {
-                    refreshPackages(toRefresh);
-                }
-            }
-        }
-
-        // Resolve bundles
-        toResolve.addAll(toStart);
-        toResolve.addAll(toRefresh);
-        removeFragmentsAndBundlesInState(toResolve, UNINSTALLED);
-        systemBundleContext.getBundle().adapt(FrameworkWiring.class).resolveBundles(toResolve);
-
-        // Compute bundles to start
-        removeFragmentsAndBundlesInState(toStart, UNINSTALLED | ACTIVE | STARTING);
-        if (!toStart.isEmpty()) {
-            // Compute correct start order
-            List<Exception> exceptions = new ArrayList<>();
-            print("Starting bundles:", verbose);
-            while (!toStart.isEmpty()) {
-                List<Bundle> bs = getBundlesToStart(toStart);
-                for (Bundle bundle : bs) {
-                    print("  " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
-                    try {
-                        bundle.start();
-                    } catch (BundleException e) {
-                        exceptions.add(e);
-                    }
-                    toStart.remove(bundle);
-                }
-            }
-            if (!exceptions.isEmpty()) {
-                throw new MultiException("Error restarting bundles", exceptions);
-            }
-        }
-
-        // Call listeners
-        // TODO: add region information and avoid flattening
-        for (Feature feature : apply(flatten(delFeatures), map(dstate.features))) {
-            callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureUninstalled, false));
-        }
-        for (Feature feature : apply(flatten(newFeatures), map(dstate.features))) {
-            callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureInstalled, false));
-        }
-
-        print("Done.", verbose);
-    }
-
-    private void propagateState(Map<Resource, RequestedState> states, Resource resource, RequestedState state, SubsystemResolver resolver) {
-        if (!isSubsystem(resource)) {
-            RequestedState reqState = mergeStates(state, states.get(resource));
-            if (reqState != states.get(resource)) {
-                states.put(resource, reqState);
-                for (Wire wire : resolver.getWiring().get(resource)) {
-                    Resource provider = wire.getProvider();
-                    RequestedState stateToMerge;
-                    String region = resolver.getBundles().get(provider);
-                    BundleInfo bi = region != null ? resolver.getBundleInfos().get(region).get(getUri(provider)) : null;
-                    if (reqState == RequestedState.Started) {
-                        String effective = wire.getCapability().getDirectives().get(CAPABILITY_EFFECTIVE_DIRECTIVE);
-                        // If there is an active effective capability or a requirement from the feature
-                        // and if the bundle is flagged as to start, start it
-                        if ((EFFECTIVE_ACTIVE.equals(effective) || IDENTITY_NAMESPACE.equals(wire.getCapability().getNamespace()))
-                                && (bi == null || bi.isStart())) {
-                            stateToMerge = RequestedState.Started;
-                        } else {
-                            stateToMerge = RequestedState.Resolved;
-                        }
-                    } else {
-                        stateToMerge = reqState;
-                    }
-                    propagateState(states, provider, stateToMerge, resolver);
-                }
-            }
-        }
     }
 
-    private boolean isSubsystem(Resource resource) {
-        for (Capability cap : resource.getCapabilities(null)) {
-            if (cap.getNamespace().equals(IDENTITY_NAMESPACE)) {
-                String type = (String) cap.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE);
-                return (type != null) && type.equals(TYPE_SUBSYSTEM);
-            }
-        }
-        return false;
-    }
-
-    private RequestedState mergeStates(RequestedState s1, RequestedState s2) {
-        if (s1 == RequestedState.Started || s2 == RequestedState.Started) {
-            return RequestedState.Started;
-        }
-        if (s1 == RequestedState.Resolved || s2 == RequestedState.Resolved) {
-            return RequestedState.Resolved;
-        }
-        return RequestedState.Installed;
-    }
-
-    private void computeBundlesToRefresh(Set<Bundle> toRefresh, Collection<Bundle> bundles, Map<Resource, Bundle> resources, Map<Resource, List<Wire>> resolution) {
-        int size;
-        do {
-            size = toRefresh.size();
-            for (Bundle bundle : bundles) {
-                // Continue if we already know about this bundle
-                if (toRefresh.contains(bundle)) {
-                    continue;
-                }
-                // Ignore non resolved bundle
-                BundleWiring wiring = bundle.adapt(BundleWiring.class);
-                if (wiring == null) {
-                    continue;
-                }
-                // Get through the old resolution and flag this bundle
-                // if it was wired to a bundle to be refreshed
-                for (BundleWire wire : wiring.getRequiredWires(null)) {
-                    if (toRefresh.contains(wire.getProvider().getBundle())) {
-                        toRefresh.add(bundle);
-                        break;
-                    }
-                }
-                // Get through the new resolution and flag this bundle
-                // if it's wired to any new bundle
-                List<Wire> newWires = resolution.get(wiring.getRevision());
-                if (newWires != null) {
-                    for (Wire wire : newWires) {
-                        Bundle b;
-                        if (wire.getProvider() instanceof BundleRevision) {
-                            b = ((BundleRevision) wire.getProvider()).getBundle();
-                        } else {
-                            b = resources.get(wire.getProvider());
-                        }
-                        if (b == null || toRefresh.contains(b)) {
-                            toRefresh.add(bundle);
-                            break;
-                        }
-                    }
-                }
-            }
-        } while (toRefresh.size() > size);
+    @Override
+    public void persistResolveRequest(Deployer.DeploymentRequest request) throws IOException {
+        writeResolve(request.requestedFeatures, request.options);
     }
 
-    private void print(String message, boolean verbose) {
-        LOGGER.info(message);
-        if (verbose) {
-            System.out.println(message);
+    @Override
+    public void installFeatureConfigs(Feature feature) throws IOException, InvalidSyntaxException {
+        if (configInstaller != null) {
+            configInstaller.installFeatureConfigs(feature);
         }
     }
 
-    private void removeFragmentsAndBundlesInState(Collection<Bundle> bundles, int state) {
-        for (Iterator<Bundle> iterator = bundles.iterator(); iterator.hasNext();) {
-            Bundle bundle = iterator.next();
-            if ((bundle.getState() & state) != 0
-                    || bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
-                iterator.remove();
-            }
+    @Override
+    public Bundle installBundle(String region, String uri, InputStream is) throws BundleException {
+        if (ROOT_REGION.equals(region)) {
+            return digraph.getRegion(region).installBundleAtLocation(uri, is);
+        } else {
+            return digraph.getRegion(region).installBundle(uri, is);
         }
     }
 
-    protected void logDeployment(Deployment overallDeployment, boolean verbose) {
-        if (overallDeployment.regions.isEmpty()) {
-            print("No deployment change.", verbose);
-            return;
-        }
-        print("Changes to perform:", verbose);
-        for (Map.Entry<String, RegionDeployment> region : overallDeployment.regions.entrySet()) {
-            RegionDeployment deployment = region.getValue();
-            print("  Region: " + region.getKey(), verbose);
-            if (!deployment.toDelete.isEmpty()) {
-                print("    Bundles to uninstall:", verbose);
-                for (Bundle bundle : deployment.toDelete) {
-                    print("      " + bundle.getSymbolicName() + " / " + bundle.getVersion(), verbose);
-                }
-            }
-            if (!deployment.toUpdate.isEmpty()) {
-                print("    Bundles to update:", verbose);
-                for (Map.Entry<Bundle, Resource> entry : deployment.toUpdate.entrySet()) {
-                    print("      " + entry.getKey().getSymbolicName() + " / " + entry.getKey().getVersion() + " with " + getUri(entry.getValue()), verbose);
-                }
-            }
-            if (!deployment.toInstall.isEmpty()) {
-                print("    Bundles to install:", verbose);
-                for (Resource resource : deployment.toInstall) {
-                    print("      " + getUri(resource), verbose);
-                }
-            }
-        }
+    @Override
+    public void updateBundle(Bundle bundle, InputStream is) throws BundleException {
+        bundle.update(is);
     }
 
-    protected Deployment computeDeployment(
-            DeploymentState dstate,
-            SubsystemResolver resolver,
-            State state) throws IOException {
-
-        Deployment result = new Deployment();
-
-        Map<String, Set<Resource>> bundlesPerRegions = resolver.getBundlesPerRegions();
-
-        // Gather all regions, including old ones and new ones
-        Set<String> regions = new HashSet<>();
-        regions.addAll(state.managedBundles.keySet());
-        regions.addAll(bundlesPerRegions.keySet());
-
-        for (String region : regions) {
-
-            RegionDeployment deployment = new RegionDeployment();
-
-            // Get the list of bundles currently assigned in the region
-            Set<Long> managed = state.managedBundles.get(region);
-            if (managed == null) {
-                managed = Collections.emptySet();
-            }
-
-            // Compute the list of resources to deploy in the region
-            Set<Resource> bundlesInRegion = bundlesPerRegions.get(region);
-            List<Resource> toDeploy = bundlesInRegion != null
-                    ? new ArrayList<>(bundlesInRegion) : new ArrayList<Resource>();
-
-            // First pass: go through all installed bundles and mark them
-            // as either to ignore or delete
-            for (long bundleId : managed) {
-                // Look for the installed bundle
-                Bundle bundle = dstate.bundles.get(bundleId);
-                // Bundle has been manually uninstalled ?
-                if (bundle != null) {
-                    // Look for a matching resource
-                    Resource resource = null;
-                    for (Resource res : toDeploy) {
-                        if (bundle.getSymbolicName().equals(getSymbolicName(res))
-                                && bundle.getVersion().equals(getVersion(res))) {
-                            resource = res;
-                            break;
-                        }
-                    }
-                    // We found a matching bundle
-                    if (resource != null) {
-                        // In case of snapshots, check if the snapshot is out of date
-                        // and flag it as to update
-                        if (isUpdateable(resource)) {
-                            // Always update snapshots
-                            if (UPDATE_SNAPSHOTS_ALWAYS.equalsIgnoreCase(updateSnaphots)) {
-                                LOGGER.debug("Update snapshot for " + bundle.getLocation());
-                                deployment.toUpdate.put(bundle, resource);
-                            } else if (UPDATE_SNAPSHOTS_CRC.equalsIgnoreCase(updateSnaphots)) {
-                                // if the checksum are different
-                                try (
-                                        InputStream is = getBundleInputStream(resource, resolver.getProviders())
-                                ) {
-                                    long newCrc = ChecksumUtils.checksum(is);
-                                    long oldCrc = state.bundleChecksums.containsKey(bundle.getBundleId()) ? state.bundleChecksums.get(bundle.getBundleId()) : 0L;
-                                    if (newCrc != oldCrc) {
-                                        LOGGER.debug("New snapshot available for " + bundle.getLocation());
-                                        deployment.toUpdate.put(bundle, resource);
-                                    }
-                                    result.bundleChecksums.put(bundle.getBundleId(), newCrc);
-                                }
-                            }
-                        }
-                        // We're done for this resource
-                        toDeploy.remove(resource);
-                        result.resToBnd.put(resource, bundle);
-                        // There's no matching resource
-                        // If the bundle is managed, we need to delete it
-                    } else if (managed.contains(bundle.getBundleId())) {
-                        deployment.toDelete.add(bundle);
-                    }
-                }
-            }
-
-            // Second pass on remaining resources
-            for (Resource resource : toDeploy) {
-                TreeMap<Version, Bundle> matching = new TreeMap<>();
-                VersionRange range = new VersionRange(Macro.transform(bundleUpdateRange, getVersion(resource).toString()));
-                for (Bundle bundle : deployment.toDelete) {
-                    if (bundle.getSymbolicName().equals(getSymbolicName(resource)) && range.contains(bundle.getVersion())) {
-                        matching.put(bundle.getVersion(), bundle);
-                    }
-                }
-                if (!matching.isEmpty()) {
-                    Bundle bundle = matching.lastEntry().getValue();
-                    deployment.toUpdate.put(bundle, resource);
-                    deployment.toDelete.remove(bundle);
-                    result.resToBnd.put(resource, bundle);
-                } else {
-                    deployment.toInstall.add(resource);
-                }
-            }
-            Collections.sort(deployment.toInstall, new ResourceComparator());
-
-            // Add this region if there is something to do
-            if (!deployment.toDelete.isEmpty()
-                    || !deployment.toUpdate.isEmpty()
-                    || !deployment.toInstall.isEmpty()) {
-                result.regions.put(region, deployment);
-            }
-        }
-
-        return result;
+    @Override
+    public void uninstall(Bundle bundle) throws BundleException {
+        bundle.uninstall();
     }
 
-    protected <T> MapUtils.Function<Bundle, T> adapt(final Class<T> clazz) {
-        return new MapUtils.Function<Bundle, T>() {
-            @Override
-            public T apply(Bundle bundle) {
-                return bundle.adapt(clazz);
-            }
-        };
+    @Override
+    public void startBundle(Bundle bundle) throws BundleException {
+        bundle.start();
     }
 
-    protected MapUtils.Function<Resource, String> featureId() {
-        return new MapUtils.Function<Resource, String>() {
-            @Override
-            public String apply(Resource resource) {
-                return getFeatureId(resource);
-            }
-        };
+    @Override
+    public void stopBundle(Bundle bundle, int options) throws BundleException {
+        bundle.stop(options);
     }
 
-
-    protected boolean isUpdateable(Resource resource) {
-        String uri = getUri(resource);
-        return getVersion(resource).getQualifier().endsWith(SNAPSHOT)
-                || uri.contains(SNAPSHOT)
-                || !uri.contains(MAVEN);
+    @Override
+    public void setBundleStartLevel(Bundle bundle, int startLevel) {
+        bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
     }
 
-    protected List<Bundle> getBundlesToStart(Collection<Bundle> bundles) {
-        // Restart the features service last, regardless of any other consideration
-        // so that we don't end up with the service trying to do stuff before we're done
-        boolean restart = false;
-
-        SortedMap<Integer, Set<Bundle>> bundlesPerStartLevel = new TreeMap<>();
-        for (Bundle bundle : bundles) {
-            if (bundle == this.bundle) {
-                restart = true;
-            } else {
-                int sl = bundle.adapt(BundleStartLevel.class).getStartLevel();
-                addToMapSet(bundlesPerStartLevel, sl, bundle);
-            }
-        }
-        if (bundlesPerStartLevel.isEmpty()) {
-            bundles = Collections.emptyList();
-        } else {
-            bundles = bundlesPerStartLevel.remove(bundlesPerStartLevel.firstKey());
-        }
-
-        // We hit FELIX-2949 if we don't use the correct order as Felix resolver isn't greedy.
-        // In order to minimize that, we make sure we resolve the bundles in the order they
-        // are given back by the resolution, meaning that all root bundles (i.e. those that were
-        // not flagged as dependencies in features) are started before the others.   This should
-        // make sure those important bundles are started first and minimize the problem.
-        List<BundleRevision> revs = new ArrayList<>();
-        for (Bundle bundle : bundles) {
-            revs.add(bundle.adapt(BundleRevision.class));
-        }
-        List<Bundle> sorted = new ArrayList<>();
-        for (BundleRevision rev : RequirementSort.sort(revs)) {
-            sorted.add(rev.getBundle());
-        }
-        if (sorted.isEmpty() && restart) {
-            sorted.add(bundle);
-        }
-        return sorted;
+    @Override
+    public void resolveBundles(Set<Bundle> bundles) {
+        systemBundleContext.getBundle().adapt(FrameworkWiring.class).resolveBundles(bundles);
     }
 
-    protected List<Bundle> getBundlesToStop(Collection<Bundle> bundles) {
-        SortedMap<Integer, Set<Bundle>> bundlesPerStartLevel = new TreeMap<>();
-        for (Bundle bundle : bundles) {
-            int sl = bundle.adapt(BundleStartLevel.class).getStartLevel();
-            addToMapSet(bundlesPerStartLevel, sl, bundle);
-        }
-        bundles = bundlesPerStartLevel.get(bundlesPerStartLevel.lastKey());
-
-        List<Bundle> bundlesToDestroy = new ArrayList<>();
-        for (Bundle bundle : bundles) {
-            ServiceReference[] references = bundle.getRegisteredServices();
-            int usage = 0;
-            if (references != null) {
-                for (ServiceReference reference : references) {
-                    usage += getServiceUsage(reference, bundles);
-                }
-            }
-            LOGGER.debug("Usage for bundle {} is {}", bundle, usage);
-            if (usage == 0) {
-                bundlesToDestroy.add(bundle);
-            }
-        }
-        if (!bundlesToDestroy.isEmpty()) {
-            Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
-                public int compare(Bundle b1, Bundle b2) {
-                    return (int) (b2.getLastModified() - b1.getLastModified());
-                }
-            });
-            LOGGER.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
-        } else {
-            ServiceReference ref = null;
-            for (Bundle bundle : bundles) {
-                ServiceReference[] references = bundle.getRegisteredServices();
-                for (ServiceReference reference : references) {
-                    if (getServiceUsage(reference, bundles) == 0) {
-                        continue;
-                    }
-                    if (ref == null || reference.compareTo(ref) < 0) {
-                        LOGGER.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
-                        ref = reference;
+    @Override
+    public void replaceDigraph(Map<String, Map<String, Map<String, Set<String>>>> policies, Map<String, Set<Long>> bundles) throws BundleException, InvalidSyntaxException {
+        RegionDigraph temp = digraph.copy();
+        // Remove everything
+        for (Region region : temp.getRegions()) {
+            temp.removeRegion(region);
+        }
+        // Re-create regions
+        for (String name : policies.keySet()) {
+            temp.createRegion(name);
+        }
+        // Dispatch bundles
+        for (Map.Entry<String, Set<Long>> entry : bundles.entrySet()) {
+            Region region = temp.getRegion(entry.getKey());
+            for (long bundleId : entry.getValue()) {
+                region.addBundle(bundleId);
+            }
+        }
+        // Add policies
+        for (Map.Entry<String, Map<String, Map<String, Set<String>>>> entry1 : policies.entrySet()) {
+            Region region1 = temp.getRegion(entry1.getKey());
+            for (Map.Entry<String, Map<String, Set<String>>> entry2 : entry1.getValue().entrySet()) {
+                Region region2 = temp.getRegion(entry2.getKey());
+                RegionFilterBuilder rfb = temp.createRegionFilterBuilder();
+                for (Map.Entry<String, Set<String>> entry3 : entry2.getValue().entrySet()) {
+                    for (String flt : entry3.getValue()) {
+                        rfb.allow(entry3.getKey(), flt);
                     }
                 }
-            }
-            if (ref != null) {
-                bundlesToDestroy.add(ref.getBundle());
-            }
-            LOGGER.debug("Selected bundle {} for destroy (lowest ranking service)", bundlesToDestroy);
-        }
-        return bundlesToDestroy;
-    }
-
-    private static int getServiceUsage(ServiceReference ref, Collection<Bundle> bundles) {
-        Bundle[] usingBundles = ref.getUsingBundles();
-        int nb = 0;
-        if (usingBundles != null) {
-            for (Bundle bundle : usingBundles) {
-                if (bundles.contains(bundle)) {
-                    nb++;
-                }
+                region1.connectRegion(region2, rfb.build());
             }
         }
-        return nb;
+        digraph.replace(temp);
     }
-
-    protected InputStream getBundleInputStream(Resource resource, Map<String, StreamProvider> providers) throws IOException {
-        String uri = getUri(resource);
-        if (uri == null) {
-            throw new IllegalStateException("Resource has no uri");
-        }
-        StreamProvider provider = providers.get(uri);
-        if (provider == null) {
-            throw new IllegalStateException("Resource " + uri + " has no StreamProvider");
-        }
-        return provider.open();
-    }
-
-    protected void refreshPackages(Collection<Bundle> bundles) throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        FrameworkWiring fw = systemBundleContext.getBundle().adapt(FrameworkWiring.class);
-        fw.refreshBundles(bundles, new FrameworkListener() {
-            @Override
-            public void frameworkEvent(FrameworkEvent event) {
-                if (event.getType() == FrameworkEvent.ERROR) {
-                    LOGGER.error("Framework error", event.getThrowable());
-                }
-                latch.countDown();
-            }
-        });
-        latch.await();
-    }
-
-
-    static class Deployment {
-        Map<Long, Long> bundleChecksums = new HashMap<>();
-        Map<Resource, Bundle> resToBnd = new HashMap<>();
-        Map<String, RegionDeployment> regions = new HashMap<>();
-    }
-
-    static class RegionDeployment {
-        List<Resource> toInstall = new ArrayList<>();
-        List<Bundle> toDelete = new ArrayList<>();
-        Map<Bundle, Resource> toUpdate = new HashMap<>();
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
index fa1fd18..b1a3ccd 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/State.java
@@ -36,14 +36,30 @@ public class State {
 
     public State copy() {
         State state = new State();
-        state.bootDone.set(bootDone.get());
-        MapUtils.copy(repositories, state.repositories);
-        MapUtils.copy(requestedFeatures, state.requestedFeatures);
-        MapUtils.copy(installedFeatures, state.installedFeatures);
-        MapUtils.copy(stateFeatures, state.stateFeatures);
-        MapUtils.copy(managedBundles, state.managedBundles);
-        MapUtils.copy(bundleChecksums, state.bundleChecksums);
+        copy(this, state, false);
         return state;
     }
 
+    public void replace(State state) {
+        copy(state, this, true);
+    }
+
+    private static void copy(State from, State to, boolean clear) {
+        if (clear) {
+            to.repositories.clear();
+            to.requestedFeatures.clear();
+            to.installedFeatures.clear();
+            to.stateFeatures.clear();
+            to.managedBundles.clear();
+            to.bundleChecksums.clear();
+        }
+        to.bootDone.set(from.bootDone.get());
+        MapUtils.copy(from.repositories, to.repositories);
+        MapUtils.copy(from.requestedFeatures, to.requestedFeatures);
+        MapUtils.copy(from.installedFeatures, to.installedFeatures);
+        MapUtils.copy(from.stateFeatures, to.stateFeatures);
+        MapUtils.copy(from.managedBundles, to.managedBundles);
+        MapUtils.copy(from.bundleChecksums, to.bundleChecksums);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/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 2609c8d..3a2f581 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
@@ -50,6 +50,7 @@ import org.junit.Before;
 import org.junit.Test;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
 
 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'>"
@@ -363,8 +364,14 @@ public class FeaturesServiceTest extends TestBase {
         URI uri = createTempRepo(FEATURE_WITH_INVALID_BUNDLE, bundle1Uri, bundle2Uri);
         
         BundleContext bundleContext = EasyMock.createMock(BundleContext.class);
+        Bundle bundle = EasyMock.createMock(Bundle.class);
+        FrameworkStartLevel fsl = EasyMock.createMock(FrameworkStartLevel.class);
         expect(bundleContext.getBundles()).andReturn(new Bundle[0]);
-        replay(bundleContext);
+        expect(bundleContext.getBundle()).andReturn(bundle);
+        expect(bundle.adapt(FrameworkStartLevel.class)).andReturn(fsl);
+        expect(fsl.getInitialBundleStartLevel()).andReturn(50);
+        expect(fsl.getStartLevel()).andReturn(100);
+        replay(bundleContext, bundle, fsl);
 
         FeaturesServiceImpl svc = new FeaturesServiceImpl(null, bundleContext, new Storage(), null, null, null, null, null, null, null, null, null);
         svc.addRepository(uri);

http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
index 5aabcb7..dc371a1 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/region/SubsystemTest.java
@@ -32,9 +32,8 @@ import java.util.Set;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
 
-import org.apache.karaf.features.Repository;
+import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.internal.download.StreamProvider;
-import org.apache.karaf.features.internal.service.FeaturesServiceImpl;
 import org.apache.karaf.features.internal.service.RepositoryImpl;
 import org.apache.karaf.features.internal.download.simple.SimpleDownloader;
 import org.junit.Test;
@@ -67,7 +66,7 @@ public class SubsystemTest {
                          features,
                          Collections.<String, Set<BundleRevision>>emptyMap(),
                          Collections.<String>emptySet(),
-                         FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                         FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
                          null);
 
         verify(resolver, expected);
@@ -98,7 +97,7 @@ public class SubsystemTest {
                          features,
                          Collections.<String, Set<BundleRevision>>emptyMap(),
                          Collections.<String>emptySet(),
-                         FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                         FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
                          null);
 
         verify(resolver, expected);
@@ -119,7 +118,7 @@ public class SubsystemTest {
                          features,
                          Collections.<String, Set<BundleRevision>>emptyMap(),
                          Collections.singleton("b"),
-                         FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                         FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
                          null);
 
         verify(resolver, expected);
@@ -139,7 +138,7 @@ public class SubsystemTest {
                 features,
                 Collections.<String, Set<BundleRevision>>emptyMap(),
                 Collections.<String>emptySet(),
-                FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
                 null);
 
         verify(resolver, expected);
@@ -161,7 +160,7 @@ public class SubsystemTest {
                 features,
                 Collections.<String, Set<BundleRevision>>emptyMap(),
                 Collections.<String>emptySet(),
-                FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE,
+                FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE,
                 null);
 
         verify(resolver, expected);