public void deploy()

in features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java [355:1075]


    public void deploy(DeploymentState dstate, DeploymentRequest request) throws Exception {
        boolean noRefreshUnmanaged = request.options.contains(FeaturesService.Option.NoAutoRefreshUnmanagedBundles);
        boolean noRefreshManaged = request.options.contains(FeaturesService.Option.NoAutoRefreshManagedBundles);
        boolean noRefresh = request.options.contains(FeaturesService.Option.NoAutoRefreshBundles);
        if (request.autoRefresh == false) {
            noRefreshUnmanaged = true;
            noRefreshManaged = true;
            noRefresh = true;
        }
        boolean noStart = request.options.contains(FeaturesService.Option.NoAutoStartBundles);
        boolean verbose = request.options.contains(FeaturesService.Option.Verbose);
        boolean simulate = request.options.contains(FeaturesService.Option.Simulate);
        boolean noManageBundles = request.options.contains(FeaturesService.Option.NoAutoManageBundles);
        boolean showWiring = request.options.contains(FeaturesService.Option.DisplayFeaturesWiring)
                    || request.options.contains(FeaturesService.Option.DisplayAllWiring);
        boolean showFeaturesWiringOnly = request.options.contains(FeaturesService.Option.DisplayFeaturesWiring)
                    && !request.options.contains(FeaturesService.Option.DisplayAllWiring);
        boolean deleteConfigurations = request.options.contains(FeaturesService.Option.DeleteConfigurations);

        // TODO: add an option to unmanage bundles instead of uninstalling those

        // current managed bundles per region, as known by o.a.k.features.internal.service.FeaturesServiceImpl.state
        Map<String, Set<Long>> managedBundles = copy(dstate.state.managedBundles);

        // current not managed (by FeaturesService state) bundles per region, as known by o.a.k.features.internal.service.BundleInstallSupportImpl.digraph
        // "unmanaged" means "not installed via features service"
        Map<String, Set<Long>> diff = diff(dstate.bundlesPerRegion, dstate.state.managedBundles);
        Map<String, Set<Bundle>> unmanagedBundles = apply(diff, map(dstate.bundles));

        // Use Subsystem and Felix resolver
        SubsystemResolver resolver = new SubsystemResolver(this.resolver, manager);
        resolver.setDeployCallback(callback);
        Map<String, Set<BundleRevision>> unmanagedBundleRevisions = apply(unmanagedBundles, adapt(BundleRevision.class));

        // preparation - creating OSGi resources with reqs and caps for regions and features
        resolver.prepare(dstate.featuresByName(), request.requirements, unmanagedBundleRevisions);

        // if some features have prerequisites, we have to deploy them first - this method may throw Exception
        // to start another cycle of deployment
        handlePrerequisites(dstate, request, resolver);

        // when there are no more prerequisites, we can resolve Subsystems and Features using Felix resolver
        // Subsystem resolver will have then full information about new bundles and bundle updates or removals
        // per region
        resolver.resolve(
                request.featureResolutionRange,
                request.serviceRequirements,
                request.globalRepository,
                request.outputFile);

        Map<String, StreamProvider> providers = resolver.getProviders();
        Map<String, Set<Resource>> featuresPerRegion = resolver.getFeaturesPerRegions();
        Map<String, Set<String>> installedFeatures = apply(featuresPerRegion, featureId());
        // changes to current state - added and removed features
        Map<String, Set<String>> newFeatures = diff(installedFeatures, dstate.state.installedFeatures);
        Map<String, Set<String>> delFeatures = diff(dstate.state.installedFeatures, installedFeatures);

        //
        // Compute requested features state
        //
        Map<String, Map<String, String>> stateFeatures = copy(dstate.state.stateFeatures);
        for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
            Map<String, String> map = stateFeatures.get(entry.getKey());
            if (map != null) {
                map.keySet().removeAll(entry.getValue());
                if (map.isEmpty()) {
                    stateFeatures.remove(entry.getKey());
                }
            }
        }
        for (Map.Entry<String, Map<String, FeatureState>> entry1 : request.stateChanges.entrySet()) {
            String region = entry1.getKey();
            Map<String, String> regionStates = stateFeatures.get(region);
            if (regionStates != null) {
                for (Map.Entry<String, FeatureState> 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.computeIfAbsent(entry.getKey(), k -> new HashMap<>());
                map.put(feature, noStart ? FeatureState.Installed.name() : FeatureState.Started.name());
            }
        }

        // Compute information for each bundle (region -> location -> BundleInfo)
        Map<String, Map<String, BundleInfo>> bundleInfos = resolver.getBundleInfos();

        //
        // Compute deployment
        //
        Deployer.Deployment deployment = computeDeployment(dstate, request, resolver);

        //
        // Compute the set of bundles to refresh
        //
        Map<Bundle, String> toRefresh = new TreeMap<>(new BundleComparator()); // sort is only used for display
        for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
            for (Bundle b : regionDeployment.toDelete) {
                toRefresh.put(b, "Bundle will be uninstalled");
            }
            for (Bundle b : regionDeployment.toUpdate.keySet()) {
                toRefresh.put(b, "Bundle will be updated");
            }
        }
        if (!noRefreshManaged) {
            computeBundlesToRefresh(toRefresh, dstate.bundles.values(), deployment.resToBnd, resolver.getWiring());
        }
        if (noRefreshUnmanaged) {
            toRefresh.keySet().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
            if (resolver.getWiring() != null) {
                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
            if (resolver.getWiring() != null) {
                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);
                }
            }
        }

        Set<Bundle> toStart = new HashSet<>();
        Set<Bundle> toResolve = new HashSet<>();
        Set<Bundle> toStop = new HashSet<>();

        //
        // Compute bundle states
        //
        Map<Resource, FeatureState> states = new HashMap<>();
        // Find all features state
        Map<Resource, FeatureState> featuresState = new HashMap<>();
        Map<Resource, Set<Resource>> conditionals = 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()) {
                Set<Resource> conditions = new HashSet<>();
                for (Wire wire : resolver.getWiring().get(feature)) {
                    if (IDENTITY_NAMESPACE.equals(wire.getRequirement().getNamespace()) &&
                            FeatureResource.CONDITIONAL_TRUE.equals(wire.getRequirement().getDirectives().get(FeatureResource.REQUIREMENT_CONDITIONAL_DIRECTIVE))) {
                        conditions.add(wire.getProvider());
                    }
                }
                if (conditions.isEmpty()) {
                    String fs = fss.get(getFeatureId(feature));
                    featuresState.put(feature, FeatureState.valueOf(fs));
                } else {
                    conditionals.put(feature, conditions);
                }
            }
        }
        // Compute conditional features state
        for (Resource feature : conditionals.keySet()) {
            FeatureState state = null;
            for (Resource cond : conditionals.get(feature)) {
                FeatureState s = featuresState.get(cond);
                if (state == null) {
                    state = s;
                } else if (state == FeatureState.Started && s == FeatureState.Resolved) {
                    state = FeatureState.Resolved;
                }
            }
            featuresState.put(feature, state);
        }
        // Propagate Resolved state
        for (Resource feature : featuresState.keySet()) {
            if (featuresState.get(feature) == FeatureState.Resolved) {
                propagateState(states, feature, FeatureState.Resolved, resolver);
            }
        }
        // Propagate Started state
        for (Resource feature : featuresState.keySet()) {
            if (featuresState.get(feature) == FeatureState.Started) {
                propagateState(states, feature, FeatureState.Started, resolver);
            }
        }
        // Put default Started state for other bundles if start attribute is true
        for (Resource resource : resolver.getBundles().keySet()) {
            BundleInfo bundleInfo = null;
            for (Map.Entry<String, Map<String, BundleInfo>> bis : resolver.getBundleInfos().entrySet()) {
                bundleInfo = bis.getValue().get(getUri(resource));
            }
            Bundle bundle = deployment.resToBnd.get(resource);
            if (bundle == null) {
                // bundle is not present, it's provided by feature
                // we are using bundleInfo and start flag
                if (bundleInfo != null && bundleInfo.isStart() && !noStart) {
                    states.put(resource, FeatureState.Started);
                } else {
                    states.put(resource, FeatureState.Resolved);
                }
            }
        }
        // Only keep bundles resources
        states.keySet().retainAll(resolver.getBundles().keySet());
        //
        // Compute bundles to start, stop and resolve
        //
        for (Map.Entry<Resource, FeatureState> 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
        //
        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() : dstate.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 > dstate.currentStartLevel) {
                                toStop.add(bundle);
                            }
                        }
                    }
                }
            }
        }

        //
        // Log wiring
        //
        if (showWiring) {
            logWiring(resolver.getWiring(), showFeaturesWiringOnly);
        }

        //
        // Log deployment
        //
        logDeployment(deployment, verbose);

        if (simulate) {
            if (!noRefresh && !toRefresh.isEmpty()) {
                print("  Bundles to refresh:", verbose);
                for (Map.Entry<Bundle, String> entry : toRefresh.entrySet()) {
                    Bundle bundle = entry.getKey();
                    print("    " + bundle.getSymbolicName() + "/" + bundle.getVersion() + " (" + entry.getValue() + ")", verbose);
                }
            }
            if (!toManage.isEmpty()) {
                print("  Managing bundle:", verbose);
                for (Bundle bundle : toManage) {
                    print("    " + bundle.getSymbolicName() + "/" + bundle.getVersion(), verbose);
                }
            }
            if (deleteConfigurations) {
                print(" Configurations to delete:", verbose);
                for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
                    for (String name : entry.getValue()) {
                        Feature feature = dstate.featuresById.get(name);
                        if (feature != null) {
                            for (ConfigInfo configInfo : feature.getConfigurations()) {
                                print("    " + configInfo.getName(), verbose);
                            }
                        }
                    }
                }
                print(" Configuration Files to delete:", verbose);
                for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
                    for (String name : entry.getValue()) {
                        Feature feature = dstate.featuresById.get(name);
                        if (feature != null) {
                            for (ConfigFileInfo configFileInfo : feature.getConfigurationFiles()) {
                                print("    " + configFileInfo.getFinalname(), verbose);
                            }
                        }
                    }
                }
            }
            return;
        }

        //
        // Execute deployment
        //
        // #1: stop bundles that needs to be updated or uninstalled or refreshed 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
        //
        Bundle serviceBundle = dstate.serviceBundle;
        Bundle configadminBundle = dstate.configadminBundle;
        //
        // Handle updates on the FeaturesService bundle
        //
        Deployer.RegionDeployment rootRegionDeployment = deployment.regions.get(ROOT_REGION);
        // We don't support uninstalling the bundle
        if (rootRegionDeployment != null && rootRegionDeployment.toDelete.contains(serviceBundle)) {
            throw new UnsupportedOperationException("Uninstalling the FeaturesService bundle is not supported");
        }

        // If the bundle needs to be updated, do the following:
        //  - persist the request 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(serviceBundle)) {
            callback.persistResolveRequest(request);
            // If the bundle is updated because of a different checksum,
            // save the new checksum persistently
            if (deployment.bundleChecksums.containsKey(serviceBundle.getBundleId())) {
                State state = dstate.state.copy();
                state.bundleChecksums.put(serviceBundle.getBundleId(),
                                          deployment.bundleChecksums.get(serviceBundle.getBundleId()));
                callback.saveState(state);
            }
            Resource resource = rootRegionDeployment.toUpdate.get(serviceBundle);
            String uri = getUri(resource);
            print("The FeaturesService bundle needs is being updated with " + uri, verbose);
            toRefresh.clear();
            toRefresh.put(serviceBundle, "FeaturesService bundle is being updated");
            computeBundlesToRefresh(toRefresh,
                    dstate.bundles.values(),
                    Collections.emptyMap(),
                    Collections.emptyMap());
            callback.stopBundle(serviceBundle, STOP_TRANSIENT);
            try (
                    InputStream is = getBundleInputStream(resource, providers)
            ) {
                callback.updateBundle(serviceBundle, uri, is);
            }
            callback.refreshPackages(toRefresh.keySet());
            callback.startBundle(serviceBundle);
            return;
        }

        callback.callListeners(DeploymentEvent.DEPLOYMENT_STARTED);

        //
        // Perform bundle operations
        //

        //
        // Stop bundles by chunks
        //
        for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
            toStop.addAll(regionDeployment.toUpdate.keySet());
            toStop.addAll(regionDeployment.toDelete);
        }
        if (!noRefresh) {
            Set<Bundle> toRefreshToStopEarly = new HashSet<>(toRefresh.keySet());
            toRefreshToStopEarly.remove(dstate.serviceBundle);
            toRefreshToStopEarly.remove(dstate.configadminBundle);
            toRefreshToStopEarly.remove(dstate.bundles.get(0L));
            toStop.addAll(toRefreshToStopEarly);
            toStart.addAll(toRefreshToStopEarly);
        }
        removeFragmentsAndBundlesInState(toStop, UNINSTALLED | RESOLVED | STOPPING | STARTING);
        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
                    callback.stopBundle(bundle, toUpdateStartLevel.containsKey(bundle) ? 0 : STOP_TRANSIENT);
                    toStop.remove(bundle);
                }
            }
        }

        //
        // Delete bundles
        //
        boolean hasToDelete = false;
        for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
            if (hasToDelete = !regionDeployment.toDelete.isEmpty()) {
                break;
            }
        }
        if (hasToDelete) {
            print("Uninstalling bundles:", verbose);
            for (Map.Entry<String, Deployer.RegionDeployment> entry : deployment.regions.entrySet()) {
                String name = entry.getKey();
                Deployer.RegionDeployment regionDeployment = entry.getValue();
                for (Bundle bundle : regionDeployment.toDelete) {
                    print("  " + bundle.getSymbolicName() + "/" + bundle.getVersion(), verbose);
                    callback.uninstall(bundle);
                    removeFromMapSet(managedBundles, name, bundle.getBundleId());
                }
            }
        }

        //
        // Update regions
        //
        {
            // Add bundles
            Map<String, Set<Long>> bundles = new HashMap<>();
            add(bundles, apply(unmanagedBundles, bundleId()));
            add(bundles, managedBundles);
            // Compute policies
            RegionDigraph computedDigraph = resolver.getFlatDigraph();
            Map<String, Map<String, Map<String, Set<String>>>> policies = copy(dstate.filtersPerRegion);
            // Only keep regions which still have bundles
            policies.keySet().retainAll(bundles.keySet());
            // 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.computeIfAbsent(name, k -> new HashMap<>());
                for (RegionDigraph.FilteredRegion fr : computedRegion.getEdges()) {
                    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
            callback.replaceDigraph(policies, bundles);
        }


        //
        // Update bundles
        //
        boolean hasToUpdate = false;
        for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
            if (hasToUpdate = !regionDeployment.toUpdate.isEmpty()) {
                break;
            }
        }
        if (hasToUpdate) {
            print("Updating bundles:", verbose);
            for (Map.Entry<String, Deployer.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)
                    ) {
                        callback.updateBundle(bundle, uri, is);
                    }
                    toStart.add(bundle);
                }
            }
        }
        //
        // Update start levels
        //
        for (Map.Entry<Bundle, Integer> entry : toUpdateStartLevel.entrySet()) {
            Bundle bundle = entry.getKey();
            int sl = entry.getValue();
            callback.setBundleStartLevel(bundle, sl);
        }
        //
        // Install bundles
        //
        boolean hasToInstall = false;
        for (Deployer.RegionDeployment regionDeployment : deployment.regions.values()) {
            if (hasToInstall = !regionDeployment.toInstall.isEmpty()) {
                break;
            }
        }
        if (hasToInstall) {
            print("Installing bundles:", verbose);
            Map<Bundle, Integer> customStartLevels = new HashMap<>();
            for (Map.Entry<String, Deployer.RegionDeployment> entry : deployment.regions.entrySet()) {
                String name = entry.getKey();
                Deployer.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))
                    ) {
                        bundle = callback.installBundle(name, uri, is);
                        crc = is.getCRC();
                    }
                    addToMapSet(managedBundles, name, bundle.getBundleId());
                    deployment.resToBnd.put(resource, bundle);
                    // save a checksum of installed snapshot bundle
                    if (FeaturesService.SnapshotUpdateBehavior.Crc == request.updateSnaphots
                            && isUpdateable(resource) && !deployment.bundleChecksums.containsKey(bundle.getBundleId())) {
                        deployment.bundleChecksums.put(bundle.getBundleId(), crc);
                    }
                    Integer startLevel = startLevels.get(resource);
                    if (startLevel != null && startLevel != dstate.initialBundleStartLevel) {
                        customStartLevels.put(bundle, startLevel);
                    }
                    FeatureState reqState = states.get(resource);
                    if (reqState == null) {
                        reqState = FeatureState.Started;
                    }
                    switch (reqState) {
                    case Started:
                        toResolve.add(bundle);
                        toStart.add(bundle);
                        break;
                    case Resolved:
                        toResolve.add(bundle);
                        break;
                    }
                }
            }

            // Set start levels after install to avoid starting before all bundles are installed
            for (Bundle bundle : customStartLevels.keySet()) {
                int startLevel = customStartLevels.get(bundle);
                bundle.adapt(BundleStartLevel.class).setStartLevel(startLevel);
            }
        }

        //
        // Update and save state
        //
        State newState = new State();
        newState.bundleChecksums.putAll(deployment.bundleChecksums);
        newState.requirements.putAll(request.requirements);
        newState.installedFeatures.putAll(installedFeatures);
        newState.stateFeatures.putAll(stateFeatures);
        newState.managedBundles.putAll(managedBundles);
        callback.saveState(newState);

        //
        // Install configurations and libraries
        //
        if (!newFeatures.isEmpty()) {
            Set<String> featureIds = flatten(newFeatures);
            for (Feature feature : dstate.featuresById.values()) {
                if (featureIds.contains(feature.getId())) {
                    callback.installConfigs(feature);
                    callback.installLibraries(feature);
                }
                for (Conditional cond : feature.getConditional()) {
                    Feature condFeature = cond.asFeature();
                    if (featureIds.contains(condFeature.getId())) {
                        callback.installConfigs(condFeature);
                        callback.installLibraries(condFeature);
                    }
                }
            }
        }

        // Delete configurations
        if (deleteConfigurations) {
            for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
                for (String name : entry.getValue()) {
                    Feature feature = dstate.featuresById.get(name);
                    callback.deleteConfigs(feature);
                }
            }
        }

        if (!noRefresh) {
            if (toRefresh.containsKey(dstate.bundles.get(0l))) {
                print("The system bundle needs to be refreshed, restarting Karaf...", verbose);
                System.setProperty("karaf.restart", "true");
                dstate.bundles.get(0l).stop();
                return;
            }

            toStop = new HashSet<>(toRefresh.keySet());
            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);
                        callback.stopBundle(bundle, STOP_TRANSIENT);
                        toStop.remove(bundle);
                        toStart.add(bundle);
                    }
                }
            }

            if (!toRefresh.isEmpty()) {
                print("Refreshing bundles:", verbose);
                for (Map.Entry<Bundle, String> entry : toRefresh.entrySet()) {
                    Bundle bundle = entry.getKey();
                    print("    " + bundle.getSymbolicName() + "/" + bundle.getVersion() + " (" + entry.getValue() + ")", verbose);
                }
                // Ensure all classes are loaded in case the bundle will be refreshed
                if (serviceBundle != null && toRefresh.containsKey(serviceBundle)) {
                    ensureAllClassesLoaded(serviceBundle);
                }
                callback.refreshPackages(toRefresh.keySet());

            }
        }

        // Resolve bundles
        toResolve.addAll(toStart);
        toResolve.addAll(toRefresh.keySet());
        removeBundlesInState(toResolve, UNINSTALLED);
        callback.callListeners(DeploymentEvent.BUNDLES_INSTALLED);
        callback.resolveBundles(toResolve, resolver.getWiring(), deployment.resToBnd);
        callback.callListeners(DeploymentEvent.BUNDLES_RESOLVED);

        // Compute bundles to start
        removeFragmentsAndBundlesInState(toStart, UNINSTALLED | ACTIVE);
        if (!toStart.isEmpty()) {
            // Compute correct start order
            List<Exception> exceptions = new ArrayList<>();
            print("Starting bundles:", verbose);
            while (!toStart.isEmpty()) {
                List<Bundle> bs = getBundlesToStart(toStart, serviceBundle);
                for (Bundle bundle : bs) {
                    print("  " + bundle.getSymbolicName() + "/" + bundle.getVersion(), verbose);
                    try {
                        callback.startBundle(bundle);
                    } catch (BundleException e) {
                        exceptions.add(e);
                    }
                    toStart.remove(bundle);
                }
            }
            if (!exceptions.isEmpty()) {
                throw new MultiException("Error restarting bundles", exceptions);
            }
        }

        // If uninstall and delete configurations, actually delete configurations and configuration files

        // Call listeners
        for (Map.Entry<String, Set<String>> entry : delFeatures.entrySet()) {
            for (String name : entry.getValue()) {
                Feature feature = dstate.featuresById.get(name);
                if (feature != null) {
                    callback.callListeners(new FeatureEvent(FeatureEvent.EventType.FeatureUninstalled, feature, entry.getKey(), false));
                }
            }
        }
        for (Map.Entry<String, Set<String>> entry : newFeatures.entrySet()) {
            for (String name : entry.getValue()) {
                Feature feature = dstate.featuresById.get(name);
                if (feature != null) {
                    callback.callListeners(new FeatureEvent(FeatureEvent.EventType.FeatureInstalled, feature, entry.getKey(), false));
                }
            }
        }
        callback.callListeners(DeploymentEvent.DEPLOYMENT_FINISHED);

        print("Done.", verbose);
    }