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:57 UTC
[5/7] git commit: [KARAF-2888] Extract the deployment for easier unit
testing
[KARAF-2888] Extract the deployment for easier unit testing
Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/9e2ada3c
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/9e2ada3c
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/9e2ada3c
Branch: refs/heads/master
Commit: 9e2ada3cc655e9ed245e3bf0c97fc3723358c6f0
Parents: 3d20404
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Tue Apr 29 22:32:54 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Wed Apr 30 20:04:27 2014 +0200
----------------------------------------------------------------------
.../apache/karaf/features/FeaturesService.java | 12 +-
.../karaf/features/internal/osgi/Activator.java | 6 +-
.../features/internal/service/Deployer.java | 1112 +++++++++++++++++
.../internal/service/FeaturesServiceImpl.java | 1171 ++----------------
.../karaf/features/internal/service/State.java | 30 +-
.../karaf/features/FeaturesServiceTest.java | 9 +-
.../features/internal/region/SubsystemTest.java | 13 +-
7 files changed, 1285 insertions(+), 1068 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
index 4366036..e266402 100644
--- a/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturesService.java
@@ -26,7 +26,17 @@ import java.util.Set;
*/
public interface FeaturesService {
- public static final String ROOT_REGION = "root";
+ String ROOT_REGION = "root";
+
+ String UPDATE_SNAPSHOTS_NONE = "none";
+ String UPDATE_SNAPSHOTS_CRC = "crc";
+ String DEFAULT_UPDATE_SNAPSHOTS = UPDATE_SNAPSHOTS_CRC;
+ String UPDATE_SNAPSHOTS_ALWAYS = "always";
+
+ String DEFAULT_FEATURE_RESOLUTION_RANGE = "${range;[====,====]}";
+ String DEFAULT_BUNDLE_UPDATE_RANGE = "${range;[==,=+)}";
+
+ String UPDATEABLE_URIS = "mvn:.*SNAPSHOT|(?!mvn:).*";
enum Option {
NoFailOnFeatureNotFound,
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
index 1be87fc..5db6204 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
@@ -159,9 +159,9 @@ public class Activator extends BaseActivator {
FeatureConfigInstaller configInstaller = new FeatureConfigInstaller(configurationAdmin);
String overrides = getString("overrides", new File(System.getProperty("karaf.etc"), "overrides.properties").toURI().toString());
- String featureResolutionRange = getString("featureResolutionRange", FeaturesServiceImpl.DEFAULT_FEATURE_RESOLUTION_RANGE);
- String bundleUpdateRange = getString("bundleUpdateRange", FeaturesServiceImpl.DEFAULT_BUNDLE_UPDATE_RANGE);
- String updateSnapshots = getString("updateSnapshots", FeaturesServiceImpl.DEFAULT_UPDATE_SNAPSHOTS);
+ String featureResolutionRange = getString("featureResolutionRange", FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE);
+ String bundleUpdateRange = getString("bundleUpdateRange", FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE);
+ String updateSnapshots = getString("updateSnapshots", FeaturesService.DEFAULT_UPDATE_SNAPSHOTS);
StateStorage stateStorage = new StateStorage() {
@Override
protected InputStream getInputStream() throws IOException {
http://git-wip-us.apache.org/repos/asf/karaf/blob/9e2ada3c/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
new file mode 100644
index 0000000..e26a07a
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/Deployer.java
@@ -0,0 +1,1112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+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.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.felix.utils.version.VersionRange;
+import org.apache.karaf.features.BundleInfo;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeatureEvent;
+import org.apache.karaf.features.FeaturesService;
+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.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;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.osgi.service.repository.Repository;
+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.FeaturesService.ROOT_REGION;
+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.FeaturesService.UPDATEABLE_URIS;
+import static org.apache.karaf.features.FeaturesService.UPDATE_SNAPSHOTS_ALWAYS;
+import static org.apache.karaf.features.FeaturesService.UPDATE_SNAPSHOTS_CRC;
+import static org.apache.karaf.features.internal.util.MapUtils.add;
+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 Deployer {
+
+ public interface DeployCallback {
+
+ void print(String message, boolean verbose);
+
+ void saveState(State state);
+ void persistResolveRequest(DeploymentRequest request) throws IOException;
+ void installFeatureConfigs(Feature feature) throws IOException, InvalidSyntaxException;
+ void callListeners(FeatureEvent featureEvent);
+
+ Bundle installBundle(String region, String uri, InputStream is) throws BundleException;
+ void updateBundle(Bundle bundle, InputStream is) throws BundleException;
+ void uninstall(Bundle bundle) throws BundleException;
+ void startBundle(Bundle bundle) throws BundleException;
+ void stopBundle(Bundle bundle, int options) throws BundleException;
+ void setBundleStartLevel(Bundle bundle, int startLevel);
+
+ void refreshPackages(Collection<Bundle> bundles) throws InterruptedException;
+ void resolveBundles(Set<Bundle> bundles);
+
+ void replaceDigraph(Map<String, Map<String,Map<String,Set<String>>>> policies, Map<String, Set<Long>> bundles) throws BundleException, InvalidSyntaxException;
+ }
+
+ static class DeploymentState {
+ State state;
+ Bundle serviceBundle;
+ int initialBundleStartLevel;
+ int currentStartLevel;
+ Map<Long, Bundle> bundles;
+ Map<String, Feature> features;
+ Map<String, Set<Long>> bundlesPerRegion;
+ Map<String, Map<String, Map<String, Set<String>>>> filtersPerRegion;
+ }
+
+ static class DeploymentRequest {
+ String overrides;
+ String featureResolutionRange;
+ String bundleUpdateRange;
+ String updateSnaphots;
+ Repository globalRepository;
+
+ Map<String, Set<String>> requestedFeatures;
+ Map<String, Map<String, FeaturesService.RequestedState>> stateChanges;
+ EnumSet<FeaturesService.Option> options;
+ }
+
+ 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<>();
+ }
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(Deployer.class);
+
+ private final DeployCallback callback;
+
+ public Deployer(DeployCallback callback) {
+ this.callback = callback;
+ }
+
+ /**
+ *
+ * @param dstate deployment state
+ * @param request deployment request
+ * @throws Exception
+ */
+ 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);
+ 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);
+
+ // TODO: add an option to unmanage bundles instead of uninstalling those
+
+ Map<String, Set<Long>> managedBundles = copy(dstate.state.managedBundles);
+
+ Map<String, Set<Bundle>> unmanagedBundles = apply(diff(dstate.bundlesPerRegion, dstate.state.managedBundles),
+ map(dstate.bundles));
+
+ // Resolve
+ // TODO: requirements
+ // TODO: bundles
+
+ SubsystemResolver resolver = new SubsystemResolver();
+ resolver.resolve(
+ dstate.features.values(),
+ request.requestedFeatures,
+ apply(unmanagedBundles, adapt(BundleRevision.class)),
+ Overrides.loadOverrides(request.overrides),
+ request.featureResolutionRange,
+ request.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, 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.entrySet().removeAll(entry.getValue());
+ if (map.isEmpty()) {
+ stateFeatures.remove(entry.getKey());
+ }
+ }
+ }
+ for (Map.Entry<String, Map<String, FeaturesService.RequestedState>> entry1 : request.stateChanges.entrySet()) {
+ String region = entry1.getKey();
+ Map<String, String> regionStates = stateFeatures.get(region);
+ if (regionStates != null) {
+ for (Map.Entry<String, FeaturesService.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 ? FeaturesService.RequestedState.Installed.name() : FeaturesService.RequestedState.Started.name());
+ }
+ }
+
+ // Compute information for each bundle
+ Map<String, Map<String, BundleInfo>> bundleInfos = resolver.getBundleInfos();
+
+ //
+ // Compute deployment
+ //
+ Deployer.Deployment deployment = computeDeployment(dstate, request, resolver);
+
+ //
+ // Compute the set of bundles to refresh
+ //
+ Set<Bundle> toRefresh = new TreeSet<>(new BundleComparator()); // sort is only used for display
+ for (Deployer.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, FeaturesService.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, FeaturesService.RequestedState.valueOf(fs), resolver);
+ }
+ }
+ states.keySet().retainAll(resolver.getBundles().keySet());
+ //
+ // Compute bundles to start, stop and resolve
+ //
+ for (Map.Entry<Resource, FeaturesService.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
+ //
+ 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);
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ //
+ // 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(dstate.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(dstate.serviceBundle)) {
+ callback.persistResolveRequest(request);
+ // If the bundle is updated because of a different checksum,
+ // save the new checksum persistently
+ if (deployment.bundleChecksums.containsKey(dstate.serviceBundle.getBundleId())) {
+ State state = dstate.state.copy();
+ state.bundleChecksums.put(dstate.serviceBundle.getBundleId(),
+ deployment.bundleChecksums.get(dstate.serviceBundle.getBundleId()));
+ callback.saveState(state);
+ }
+ Resource resource = rootRegionDeployment.toUpdate.get(dstate.serviceBundle);
+ String uri = getUri(resource);
+ print("The FeaturesService bundle needs is being updated with " + uri, verbose);
+ toRefresh.clear();
+ toRefresh.add(dstate.serviceBundle);
+ computeBundlesToRefresh(toRefresh,
+ dstate.bundles.values(),
+ Collections.<Resource, Bundle>emptyMap(),
+ Collections.<Resource, List<Wire>>emptyMap());
+ callback.stopBundle(dstate.serviceBundle, STOP_TRANSIENT);
+ try (
+ InputStream is = getBundleInputStream(resource, providers)
+ ) {
+ callback.updateBundle(dstate.serviceBundle, is);
+ }
+ callback.refreshPackages(toRefresh);
+ callback.startBundle(dstate.serviceBundle);
+ return;
+ }
+
+ //
+ // Perform bundle operations
+ //
+
+ //
+ // Stop bundles by chunks
+ //
+ for (Deployer.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
+ 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.get(name);
+ if (policy == null) {
+ policy = new HashMap<>();
+ policies.put(name, policy);
+ }
+ 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, 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);
+ 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 (UPDATE_SNAPSHOTS_CRC.equals(request.updateSnaphots)
+ && isUpdateable(resource) && !deployment.bundleChecksums.containsKey(bundle.getBundleId())) {
+ deployment.bundleChecksums.put(bundle.getBundleId(), crc);
+ }
+ int startLevel = startLevels.get(resource);
+ callback.setBundleStartLevel(bundle, startLevel);
+ FeaturesService.RequestedState reqState = states.get(resource);
+ switch (reqState) {
+ case Started:
+ toResolve.add(bundle);
+ toStart.add(bundle);
+ break;
+ case Resolved:
+ toResolve.add(bundle);
+ break;
+ }
+ }
+ }
+ }
+
+ //
+ // Update and save state
+ //
+ State newState = new State();
+ newState.bundleChecksums.putAll(deployment.bundleChecksums);
+ newState.requestedFeatures.putAll(request.requestedFeatures);
+ newState.installedFeatures.putAll(installedFeatures);
+ newState.stateFeatures.putAll(stateFeatures);
+ newState.managedBundles.putAll(managedBundles);
+ callback.saveState(newState);
+
+ //
+ // Install configurations
+ //
+ if (!newFeatures.isEmpty()) {
+ Set<Feature> set = apply(flatten(newFeatures), map(dstate.features));
+ for (Feature feature : set) {
+ callback.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);
+ callback.stopBundle(bundle, 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()) {
+ callback.refreshPackages(toRefresh);
+ }
+ }
+ }
+
+ // Resolve bundles
+ toResolve.addAll(toStart);
+ toResolve.addAll(toRefresh);
+ removeFragmentsAndBundlesInState(toResolve, UNINSTALLED);
+ callback.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, dstate.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);
+ }
+ }
+
+ // Call listeners
+ // TODO: add region information and avoid flattening
+ for (Feature feature : apply(flatten(delFeatures), map(dstate.features))) {
+ callback.callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureUninstalled, false));
+ }
+ for (Feature feature : apply(flatten(newFeatures), map(dstate.features))) {
+ callback.callListeners(new FeatureEvent(feature, FeatureEvent.EventType.FeatureInstalled, false));
+ }
+
+ print("Done.", verbose);
+ }
+
+ private void propagateState(Map<Resource, FeaturesService.RequestedState> states, Resource resource, FeaturesService.RequestedState state, SubsystemResolver resolver) {
+ if (!isSubsystem(resource)) {
+ FeaturesService.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();
+ FeaturesService.RequestedState stateToMerge;
+ String region = resolver.getBundles().get(provider);
+ BundleInfo bi = region != null ? resolver.getBundleInfos().get(region).get(getUri(provider)) : null;
+ if (reqState == FeaturesService.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 = FeaturesService.RequestedState.Started;
+ } else {
+ stateToMerge = FeaturesService.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 FeaturesService.RequestedState mergeStates(FeaturesService.RequestedState s1, FeaturesService.RequestedState s2) {
+ if (s1 == FeaturesService.RequestedState.Started || s2 == FeaturesService.RequestedState.Started) {
+ return FeaturesService.RequestedState.Started;
+ }
+ if (s1 == FeaturesService.RequestedState.Resolved || s2 == FeaturesService.RequestedState.Resolved) {
+ return FeaturesService.RequestedState.Resolved;
+ }
+ return FeaturesService.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);
+ }
+
+ private void print(String message, boolean verbose) {
+ callback.print(message, verbose);
+ }
+
+ 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();
+ }
+ }
+ }
+
+ protected void logDeployment(Deployer.Deployment overallDeployment, boolean verbose) {
+ if (overallDeployment.regions.isEmpty()) {
+ print("No deployment change.", verbose);
+ return;
+ }
+ print("Changes to perform:", verbose);
+ for (Map.Entry<String, Deployer.RegionDeployment> region : overallDeployment.regions.entrySet()) {
+ Deployer.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);
+ }
+ }
+ }
+ }
+
+ protected Deployment computeDeployment(
+ DeploymentState dstate,
+ DeploymentRequest request,
+ SubsystemResolver resolver) 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(dstate.state.managedBundles.keySet());
+ regions.addAll(bundlesPerRegions.keySet());
+
+ for (String region : regions) {
+
+ Deployer.RegionDeployment deployment = new Deployer.RegionDeployment();
+
+ // Get the list of bundles currently assigned in the region
+ Set<Long> managed = dstate.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(request.updateSnaphots)) {
+ LOGGER.debug("Update snapshot for " + bundle.getLocation());
+ deployment.toUpdate.put(bundle, resource);
+ } else if (UPDATE_SNAPSHOTS_CRC.equalsIgnoreCase(request.updateSnaphots)) {
+ // if the checksum are different
+ try (
+ InputStream is = getBundleInputStream(resource, resolver.getProviders())
+ ) {
+ long newCrc = ChecksumUtils.checksum(is);
+ long oldCrc = dstate.state.bundleChecksums.containsKey(bundle.getBundleId())
+ ? dstate.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(request.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;
+ }
+
+ 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);
+ }
+ };
+ }
+
+ protected MapUtils.Function<Bundle, Long> bundleId() {
+ return new MapUtils.Function<Bundle, Long>() {
+ @Override
+ public Long apply(Bundle bundle) {
+ return bundle.getBundleId();
+ }
+ };
+ }
+
+ protected MapUtils.Function<Resource, String> featureId() {
+ return new MapUtils.Function<Resource, String>() {
+ @Override
+ public String apply(Resource resource) {
+ return getFeatureId(resource);
+ }
+ };
+ }
+
+ protected boolean isUpdateable(Resource resource) {
+ String uri = getUri(resource);
+ return uri.matches(UPDATEABLE_URIS);
+ }
+
+ protected List<Bundle> getBundlesToStart(Collection<Bundle> bundles, Bundle serviceBundle) {
+ // 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 == serviceBundle) {
+ 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(serviceBundle);
+ }
+ return sorted;
+ }
+
+ 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;
+ }
+ }
+ }
+ 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++;
+ }
+ }
+ }
+ return nb;
+ }
+
+ 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();
+ }
+
+}