org.apache.karaf.features.internal.service.Deployer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.karaf.features.core Show documentation
Show all versions of org.apache.karaf.features.core Show documentation
This bundle is the core implementation of the Karaf features support.
/*
* 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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
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.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;
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.Conditional;
import org.apache.karaf.features.ConfigFileInfo;
import org.apache.karaf.features.ConfigInfo;
import org.apache.karaf.features.DeploymentEvent;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeatureEvent;
import org.apache.karaf.features.FeatureState;
import org.apache.karaf.features.FeaturesService;
import org.apache.karaf.features.internal.download.DownloadManager;
import org.apache.karaf.features.internal.download.StreamProvider;
import org.apache.karaf.features.internal.region.SubsystemResolver;
import org.apache.karaf.features.internal.region.SubsystemResolverCallback;
import org.apache.karaf.features.internal.resolver.FeatureResource;
import org.apache.karaf.features.internal.resolver.ResolverUtil;
import org.apache.karaf.features.internal.resolver.ResourceUtils;
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.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.namespace.BundleNamespace;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.PackageNamespace;
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.Namespace;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.resource.Wire;
import org.osgi.service.repository.Repository;
import org.osgi.service.resolver.Resolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.karaf.features.FeaturesService.ROOT_REGION;
import static org.apache.karaf.features.FeaturesService.UPDATEABLE_URIS;
import static org.apache.karaf.features.internal.resolver.ResolverUtil.getSymbolicName;
import static org.apache.karaf.features.internal.resolver.ResolverUtil.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.getType;
import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri;
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.HostNamespace.HOST_NAMESPACE;
import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
import static org.osgi.framework.namespace.IdentityNamespace.TYPE_BUNDLE;
public class Deployer {
/**
* Interface through which {@link Deployer} interacts with OSGi framework.
*/
public interface DeployCallback extends SubsystemResolverCallback {
void print(String message, boolean verbose);
void saveState(State state);
void persistResolveRequest(DeploymentRequest request) throws IOException;
void callListeners(DeploymentEvent deployEvent);
void callListeners(FeatureEvent featureEvent);
Bundle installBundle(String region, String uri, InputStream is) throws BundleException;
void updateBundle(Bundle bundle, String uri, 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 resolveBundles(Set bundles, Map> wiring,
Map resToBnd);
void refreshPackages(Collection bundles) throws InterruptedException;
void replaceDigraph(Map>>> policies,
Map> bundles) throws BundleException, InvalidSyntaxException;
void installConfigs(Feature feature) throws IOException, InvalidSyntaxException;
void deleteConfigs(Feature feature) throws IOException, InvalidSyntaxException;
void installLibraries(Feature feature) throws IOException;
}
@SuppressWarnings("serial")
public static class CircularPrerequisiteException extends Exception {
private final Set prereqs;
public CircularPrerequisiteException(Set prereqs) {
super(prereqs.toString());
this.prereqs = prereqs;
}
public Set getPrereqs() {
return prereqs;
}
}
@SuppressWarnings("serial")
public static class PartialDeploymentException extends Exception {
private final Set missing;
public PartialDeploymentException(Set missing) {
this.missing = missing;
}
public Set getMissing() {
return missing;
}
}
/**
* Representation of the state of system from the point of view of installed bundles
* and available features
*/
public static class DeploymentState {
// part of the deployment state related to features service
/** Current {@link State} of features service */
public State state;
// part of the deployment state related to low level OSGi framework (bundles, no regions)
/** A {@link Bundle} providing {@link FeaturesService} */
public Bundle serviceBundle;
/** A {@link Bundle} providing {@link org.osgi.service.cm.ConfigurationAdmin} service */
public Bundle configadminBundle;
/** {@link org.osgi.framework.startlevel.FrameworkStartLevel#getInitialBundleStartLevel()} */
public int initialBundleStartLevel;
/** {@link org.osgi.framework.startlevel.FrameworkStartLevel#getStartLevel()} */
public int currentStartLevel;
/** bundle-id -> bundle for all currently installed bundles */
public Map bundles;
// part of the deployment state related to all available features
/** feature-name -> list of features for different versions for all available features (not only installed) */
private Map> features;
/** feature-id -> feature (not only installed) */
private Map featuresById;
// part of the deployment state related to regions
/** region-name -> ids for bundles installed in region (see {@link State#managedBundles}) */
public Map> bundlesPerRegion;
/** region-name -> connected, filtered, region-name -> filter-namespace -> filters */
public Map>>> filtersPerRegion;
/**
* Returns all features indexed by their name. For each name we have collection of {@link Feature features}
* for different versions.
* @return
*/
public Map> featuresByName() {
return features;
}
/**
* Returns all features indexed by their id.
* @return
*/
public Map featuresById() {
return featuresById;
}
/**
* Sets a list of features and stores it as map of features where the key is name
and value is a
* list of features with different versions.
* @param featuresList
*/
public void partitionFeatures(Collection featuresList) {
features = new HashMap<>();
featuresById = new HashMap<>();
for (Feature feature : featuresList) {
features.computeIfAbsent(feature.getName(), name -> new ArrayList<>()).add(feature);
featuresById.put(feature.getId(), feature);
}
}
}
/**
* A request to change current {@link DeploymentState} of system
* {@link #requirements} specify target set of system requirements. If new features are installed,
* requirements should include currently installed features and new ones. If features are being uninstalled,
* requirements should include currently installed features minus the ones that are removed.
*/
public static class DeploymentRequest {
/** A bnd macro that changes feature version into a version range. */
public String featureResolutionRange;
/** Indication of how to handle requirements from osgi.service
namespace */
public FeaturesService.ServiceRequirementsBehavior serviceRequirements;
/** A bnd macro to find update'able version range for bundle versions (e.g., to determine whether to install or update a bundle */
public String bundleUpdateRange;
/** Indication of when to update bundles (or leave them as they are currently installed) */
public FeaturesService.SnapshotUpdateBehavior updateSnaphots;
/**
* Additional {@link Repository} that'll be used to resolve unresolved, non-optional requirements if
* they're not resolved against current
*/
public Repository globalRepository;
/** Target/desired set of requirements per region */
public Map> requirements;
/** Target/desired set of features state per region */
public Map> stateChanges;
/** Deployment options */
public EnumSet options;
/** File to store result of deployment */
public String outputFile;
/**
* Prepare standard, empty DeploymentRequest, where feature versions are taken literally (no ranges)
* and bundle updates use natural range to determine between install and update (update on micro
* digit in version, e.g., 2.1.0
-> 2.1.2
, but not 2.1.2
->
* 2.2.0
).
* @return
*/
public static DeploymentRequest defaultDeploymentRequest() {
DeploymentRequest request = new DeploymentRequest();
request.bundleUpdateRange = FeaturesService.DEFAULT_BUNDLE_UPDATE_RANGE;
request.featureResolutionRange = FeaturesService.DEFAULT_FEATURE_RESOLUTION_RANGE;
request.serviceRequirements = FeaturesService.ServiceRequirementsBehavior.Default;
request.requirements = new HashMap<>();
request.stateChanges = new HashMap<>();
request.options = EnumSet.noneOf(FeaturesService.Option.class);
return request;
}
}
/**
* Deployment information for all regions
*/
static class Deployment {
Map bundleChecksums = new HashMap<>();
Map resToBnd = new HashMap<>();
Map regions = new HashMap<>();
}
/**
* Deployment information for single region
*/
static class RegionDeployment {
/** new {@link Resource resources} to install */
List toInstall = new ArrayList<>();
/** existing {@link Bundle bundles} to remove */
List toDelete = new ArrayList<>();
/** existing {@link Bundle bundles} to update using new {@link Resource resources} */
Map toUpdate = new HashMap<>();
}
private static final Logger LOGGER = LoggerFactory.getLogger(Deployer.class);
private final DownloadManager manager;
private final Resolver resolver;
private final DeployCallback callback;
public Deployer(DownloadManager manager, Resolver resolver, DeployCallback callback) {
this.manager = manager;
this.resolver = resolver;
this.callback = callback;
}
/**
* Performs full deployment - with prerequisites
*
* @param dstate deployment state
* @param request deployment request
* @throws Exception in case of deployment failure.
*/
public void deployFully(DeploymentState dstate, DeploymentRequest request) throws Exception {
Set prereqs = new HashSet<>();
while (true) {
try {
deploy(dstate, request);
break;
} catch (Deployer.PartialDeploymentException e) {
if (!prereqs.containsAll(e.getMissing())) {
prereqs.addAll(e.getMissing());
} else {
throw new Exception("Deployment aborted due to loop in missing prerequisites: " + e.getMissing());
}
}
}
}
/**
* Perform a deployment.
*
* @param dstate deployment state
* @param request deployment request
* @throws Exception in case of deployment failure.
*/
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);
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> 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> diff = diff(dstate.bundlesPerRegion, dstate.state.managedBundles);
Map> unmanagedBundles = apply(diff, map(dstate.bundles));
// Use Subsystem and Felix resolver
SubsystemResolver resolver = new SubsystemResolver(this.resolver, manager);
resolver.setDeployCallback(callback);
Map> 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 providers = resolver.getProviders();
Map> featuresPerRegion = resolver.getFeaturesPerRegions();
Map> installedFeatures = apply(featuresPerRegion, featureId());
// changes to current state - added and removed features
Map> newFeatures = diff(installedFeatures, dstate.state.installedFeatures);
Map> delFeatures = diff(dstate.state.installedFeatures, installedFeatures);
//
// Compute requested features state
//
Map> stateFeatures = copy(dstate.state.stateFeatures);
for (Map.Entry> entry : delFeatures.entrySet()) {
Map map = stateFeatures.get(entry.getKey());
if (map != null) {
map.keySet().removeAll(entry.getValue());
if (map.isEmpty()) {
stateFeatures.remove(entry.getKey());
}
}
}
for (Map.Entry> entry1 : request.stateChanges.entrySet()) {
String region = entry1.getKey();
Map regionStates = stateFeatures.get(region);
if (regionStates != null) {
for (Map.Entry entry2 : entry1.getValue().entrySet()) {
String feature = entry2.getKey();
if (regionStates.containsKey(feature)) {
regionStates.put(feature, entry2.getValue().name());
}
}
}
}
for (Map.Entry> entry : newFeatures.entrySet()) {
for (String feature : entry.getValue()) {
Map 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> bundleInfos = resolver.getBundleInfos();
//
// Compute deployment
//
Deployer.Deployment deployment = computeDeployment(dstate, request, resolver);
//
// Compute the set of bundles to refresh
//
Map 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 toManage = new TreeSet<>(new BundleComparator()); // sort is only used for display
if (!noManageBundles) {
Set features = resolver.getFeatures().keySet();
Set unmanaged = apply(flatten(unmanagedBundles), adapt(BundleRevision.class));
Set requested = new HashSet<>();
// Gather bundles required by a feature
if (resolver.getWiring() != null) {
for (List 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 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 bundleToRegion = new HashMap<>();
for (Map.Entry> 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 toStart = new HashSet<>();
Set toResolve = new HashSet<>();
Set toStop = new HashSet<>();
//
// Compute bundle states
//
Map states = new HashMap<>();
// Find all features state
Map featuresState = new HashMap<>();
Map> conditionals = new HashMap<>();
for (Map.Entry> entry : resolver.getFeaturesPerRegions().entrySet()) {
String region = entry.getKey();
Map fss = stateFeatures.get(region);
for (Resource feature : entry.getValue()) {
Set 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> 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 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 startLevels = new HashMap<>();
Map toUpdateStartLevel = new HashMap<>();
for (Map.Entry> 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 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> 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> 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 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 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 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> bundles = new HashMap<>();
add(bundles, apply(unmanagedBundles, bundleId()));
add(bundles, managedBundles);
// Compute policies
RegionDigraph computedDigraph = resolver.getFlatDigraph();
Map>>> 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>> policy = policies.computeIfAbsent(name, k -> new HashMap<>());
for (RegionDigraph.FilteredRegion fr : computedRegion.getEdges()) {
String r2 = fr.getRegion().getName();
Map> filters = new HashMap<>();
Map> 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 rde : deployment.regions.entrySet()) {
for (Map.Entry 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 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 customStartLevels = new HashMap<>();
for (Map.Entry 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 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> 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 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 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 exceptions = new ArrayList<>();
print("Starting bundles:", verbose);
while (!toStart.isEmpty()) {
List 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> 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> 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);
}
private void handlePrerequisites(DeploymentState dstate, DeploymentRequest request, SubsystemResolver resolver)
throws Exception {
Set prereqs = resolver.collectPrerequisites();
if (!prereqs.isEmpty()) {
for (Iterator iterator = prereqs.iterator(); iterator.hasNext(); ) {
String prereq = iterator.next();
String[] parts = prereq.split("/");
String name = parts[0];
String version = parts[1];
VersionRange range = getRange(version, request.featureResolutionRange);
boolean found = false;
for (Set featureSet : dstate.state.installedFeatures.values()) {
for (String feature : featureSet) {
String[] p = feature.split("/");
found = name.equals(p[0]) && range.contains(VersionTable.getVersion(p[1]));
if (found) {
break;
}
}
if (found) {
break;
}
}
if (found) {
iterator.remove();
}
}
}
if (!prereqs.isEmpty()) {
if (request.requirements.get(ROOT_REGION).containsAll(prereqs)) {
throw new CircularPrerequisiteException(prereqs);
}
DeploymentRequest newRequest = new DeploymentRequest();
newRequest.bundleUpdateRange = request.bundleUpdateRange;
newRequest.featureResolutionRange = request.featureResolutionRange;
newRequest.serviceRequirements = request.serviceRequirements;
newRequest.globalRepository = request.globalRepository;
newRequest.options = request.options;
newRequest.requirements = copy(dstate.state.requirements);
for (String prereq : prereqs) {
addToMapSet(newRequest.requirements, ROOT_REGION, new FeatureReq(prereq).toRequirement());
}
newRequest.stateChanges = Collections.emptyMap();
newRequest.updateSnaphots = request.updateSnaphots;
deploy(dstate, newRequest);
throw new PartialDeploymentException(prereqs);
}
}
private static VersionRange getRange(String version, String featureResolutionRange) {
VersionRange range;
if (version.equals("0.0.0")) {
range = VersionRange.ANY_VERSION;
} else if (!version.startsWith("[") && !version.startsWith("(")) {
range = new VersionRange(Macro.transform(featureResolutionRange, version));
} else {
range = new VersionRange(version);
}
return range;
}
private void propagateState(Map states, Resource resource, FeatureState state, SubsystemResolver resolver) {
if (!isSubsystem(resource)) {
FeatureState reqState = mergeStates(state, states.get(resource));
if (reqState != states.get(resource)) {
states.put(resource, reqState);
for (Wire wire : resolver.getWiring().get(resource)) {
if (IDENTITY_NAMESPACE.equals(wire.getCapability().getNamespace())) {
propagateState(states, wire.getProvider(), reqState, resolver);
}
}
}
}
}
private static boolean isSubsystem(Resource resource) {
return TYPE_SUBSYSTEM.equals(getType(resource));
}
private static boolean isBundle(Resource resource) {
return TYPE_BUNDLE.equals(getType(resource));
}
/**
* Returns the most active state of the given states
*/
private static FeatureState mergeStates(FeatureState s1, FeatureState s2) {
if (s1 == FeatureState.Started || s2 == FeatureState.Started) {
return FeatureState.Started;
}
if (s1 == FeatureState.Resolved || s2 == FeatureState.Resolved) {
return FeatureState.Resolved;
}
return FeatureState.Installed;
}
private static void computeBundlesToRefresh(Map toRefresh, Collection bundles, Map resources, Map> resolution) {
// Compute the new list of fragments
Map> newFragments = new HashMap<>();
for (Bundle bundle : bundles) {
newFragments.put(bundle, new HashSet<>());
}
if (resolution != null) {
for (Resource res : resolution.keySet()) {
for (Wire wire : resolution.get(res)) {
if (HOST_NAMESPACE.equals(wire.getCapability().getNamespace())) {
Bundle bundle;
if (wire.getProvider() instanceof BundleRevision) {
bundle = ((BundleRevision) wire.getProvider()).getBundle();
} else {
bundle = resources.get(wire.getProvider());
}
if (bundle != null) {
Bundle b = resources.get(wire.getRequirer());
Resource r = b != null ? b.adapt(BundleRevision.class) : wire.getRequirer();
newFragments.get(bundle).add(r);
}
}
}
}
}
// Main loop
int size;
Map bndToRes = new HashMap<>();
for (Map.Entry entry : resources.entrySet()) {
bndToRes.put(entry.getValue(), entry.getKey());
}
do {
size = toRefresh.size();
main: for (Bundle bundle : bundles) {
Resource resource = bndToRes.get(bundle);
// This bundle is not managed
if (resource == null) {
resource = bundle.adapt(BundleRevision.class);
}
// Continue if we already know about this bundle
if (toRefresh.containsKey(bundle)) {
continue;
}
// Ignore non resolved bundle
BundleWiring wiring = bundle.adapt(BundleWiring.class);
if (wiring == null) {
continue;
}
// Ignore bundles that won't be wired
List newWires = resolution != null ? resolution.get(resource) : null;
if (newWires == null) {
continue;
}
// Check if this bundle is a host and its fragments changed
Set oldFragments = new HashSet<>();
for (BundleWire wire : wiring.getProvidedWires(null)) {
if (HOST_NAMESPACE.equals(wire.getCapability().getNamespace())) {
oldFragments.add(wire.getRequirer());
}
}
if (!oldFragments.containsAll(newFragments.get(bundle))) {
toRefresh.put(bundle, "Attached fragments changed: " + new ArrayList<>(newFragments.get(bundle)));
break;
}
// Compare the old and new resolutions
Set wiredBundles = new HashSet<>();
wiredBundles.add(new BundleWrapper(bundle));
for (BundleWire wire : wiring.getRequiredWires(null)) {
BundleRevision rev = wire.getProvider();
Bundle provider = rev.getBundle();
if (toRefresh.containsKey(provider)) {
// The bundle is wired to a bundle being refreshed,
// so we need to refresh it too
toRefresh.put(bundle, "Wired to " + provider.getSymbolicName() + "/" + provider.getVersion() + " which is being refreshed");
continue main;
}
wiredBundles.add(new BundleWrapper(provider));
}
Map wiredResources = new HashMap<>();
for (Wire wire : newWires) {
// Handle only packages, hosts, and required bundles
String namespace = wire.getRequirement().getNamespace();
if (!namespace.equals(BundleNamespace.BUNDLE_NAMESPACE)
&& !namespace.equals(PackageNamespace.PACKAGE_NAMESPACE)
&& !namespace.equals(HostNamespace.HOST_NAMESPACE)) {
continue;
}
// Ignore non-resolution time requirements
String effective = wire.getRequirement().getDirectives().get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
if (effective != null && !Namespace.EFFECTIVE_RESOLVE.equals(effective)) {
continue;
}
// Ignore non bundle resources
if (!isBundle(wire.getProvider())) {
continue;
}
BundleWrapper bw = new BundleWrapper(wire.getProvider());
if (!wiredResources.containsKey(bw)) {
wiredResources.put(bw, wire.getRequirement());
}
}
if (!wiredBundles.containsAll(wiredResources.keySet())) {
Map newResources = new HashMap<>(wiredResources);
newResources.keySet().removeAll(wiredBundles);
StringBuilder sb = new StringBuilder();
sb.append("Should be wired to: ");
boolean first = true;
for (Map.Entry entry : newResources.entrySet()) {
if (!first) {
sb.append(", ");
} else {
first = false;
}
Requirement req = entry.getValue();
sb.append(entry.getKey());
sb.append(" (through ");
sb.append(req);
sb.append(")");
}
toRefresh.put(bundle, sb.toString());
}
}
} while (toRefresh.size() > size);
}
private void print(String message, boolean verbose) {
callback.print(message, verbose);
}
private static void removeFragmentsAndBundlesInState(Collection bundles, int state) {
bundles.removeIf(bundle -> (bundle.getState() & state) != 0
|| bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null);
}
private static void removeBundlesInState(Collection bundles, int state) {
bundles.removeIf(bundle -> (bundle.getState() & state) != 0);
}
protected void logWiring(Map> wiring, boolean onlyFeatures) {
print("Wiring:", true);
Map> wires = new HashMap<>();
for (Resource r : wiring.keySet()) {
if (onlyFeatures && !ResourceUtils.TYPE_FEATURE.equals(ResourceUtils.getType(r))) {
continue;
}
for (Wire w : wiring.get(r)) {
if (onlyFeatures && !ResourceUtils.TYPE_FEATURE.equals(ResourceUtils.getType(w.getProvider()))) {
continue;
}
MapUtils.addToMapSet(wires, w.getRequirer(), w.getProvider());
}
}
List sorted = new ArrayList<>(wires.keySet());
sorted.sort(Comparator.comparingInt(r2 -> wires.get(r2).size()));
for (Resource r : sorted) {
print(" " + ResourceUtils.getType(r) + ": " + ResolverUtil.getSymbolicName(r) + " / " + ResolverUtil.getVersion(r), true);
for (Resource w : wires.get(r)) {
print(" " + ResourceUtils.getType(w) + ": " + ResolverUtil.getSymbolicName(w) + " / " + ResolverUtil.getVersion(w), true);
}
}
}
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 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 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> bundlesPerRegions = resolver.getBundlesPerRegions();
// Gather all regions, including old ones and new ones
Set 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 managed = dstate.state.managedBundles.get(region);
if (managed == null) {
managed = Collections.emptySet();
}
// Compute the list of resources to deploy in the region
Set bundlesInRegion = bundlesPerRegions.get(region);
List toDeploy = bundlesInRegion != null ? new ArrayList<>(bundlesInRegion) : new ArrayList<>();
// Remove the system bundle
Bundle systemBundle = dstate.bundles.get(0l);
if (systemBundle != null) {
// It may be null when unit testing, so ignore that
toDeploy.remove(systemBundle.adapt(BundleRevision.class));
}
// 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 (FeaturesService.SnapshotUpdateBehavior.Always == request.updateSnaphots) {
LOGGER.debug("Update snapshot for " + bundle.getLocation());
deployment.toUpdate.put(bundle, resource);
} else if (FeaturesService.SnapshotUpdateBehavior.Crc == request.updateSnaphots) {
// Retrieve current bundle checksum
long oldCrc;
if (dstate.state.bundleChecksums.containsKey(bundleId)) {
oldCrc = dstate.state.bundleChecksums.get(bundleId);
} else {
// Load bundle checksums if not already done
// This is a bit hacky, but we can't get a hold on the real bundle location
// in a standard way in OSGi. Therefore, hack into Felix/Equinox to obtain the
// corresponding jar url and use that one to compute the checksum of the bundle.
oldCrc = 0l;
try {
URL url = bundle.getEntry("META-INF/MANIFEST.MF");
URLConnection con = url.openConnection();
Method method = con.getClass().getDeclaredMethod("getLocalURL");
method.setAccessible(true);
String jarUrl = ((URL) method.invoke(con)).toExternalForm();
if (jarUrl.startsWith("jar:")) {
String jar = jarUrl.substring("jar:".length(), jarUrl.indexOf("!/"));
jar = new URL(jar).getFile();
try (InputStream is = new FileInputStream(jar)) {
oldCrc = ChecksumUtils.checksum(is);
}
result.bundleChecksums.put(bundleId, oldCrc);
}
} catch (Throwable t) {
LOGGER.debug("Error calculating checksum for bundle: {}", bundle, t);
}
}
// Compute new bundle checksum
long newCrc;
try (
InputStream is = getBundleInputStream(resource, resolver.getProviders())
) {
newCrc = ChecksumUtils.checksum(is);
result.bundleChecksums.put(bundle.getBundleId(), newCrc);
}
// if the checksum are different
if (newCrc != oldCrc) {
LOGGER.debug("New snapshot available for " + bundle.getLocation());
deployment.toUpdate.put(bundle, resource);
}
}
}
// 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 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);
}
}
deployment.toInstall.sort(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 Function adapt(final Class clazz) {
return bundle -> bundle.adapt(clazz);
}
protected Function bundleId() {
return Bundle::getBundleId;
}
protected Function featureId() {
return ResourceUtils::getFeatureId;
}
protected boolean isUpdateable(Resource resource) {
String uri = getUri(resource);
return uri != null && uri.matches(UPDATEABLE_URIS);
}
protected List getBundlesToStart(Collection 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> 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 revs = new ArrayList<>();
for (Bundle bundle : bundles) {
revs.add(bundle.adapt(BundleRevision.class));
}
List sorted = new ArrayList<>();
for (BundleRevision rev : RequirementSort.sort(revs)) {
sorted.add(rev.getBundle());
}
if (sorted.isEmpty() && restart) {
sorted.add(serviceBundle);
}
return sorted;
}
@SuppressWarnings("rawtypes")
protected List getBundlesToStop(Collection bundles) {
SortedMap> bundlesPerStartLevel = new TreeMap<>();
for (Bundle bundle : bundles) {
int sl = bundle.adapt(BundleStartLevel.class).getStartLevel();
addToMapSet(bundlesPerStartLevel, sl, bundle);
}
bundles = bundlesPerStartLevel.get(bundlesPerStartLevel.lastKey());
List 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()) {
bundlesToDestroy.sort((b1, b2) -> Long.compare(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 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 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) {
return new URL(uri).openStream();
// throw new IllegalStateException("Resource " + uri + " has no StreamProvider");
}
return provider.open();
}
public static void ensureAllClassesLoaded(Bundle bundle) throws ClassNotFoundException {
BundleWiring wiring = bundle.adapt(BundleWiring.class);
if (wiring != null) {
for (String path : wiring.listResources("/", "*.class", BundleWiring.LISTRESOURCES_RECURSE)) {
String className = path.substring(0, path.length() - ".class".length());
className = className.replace('/', '.');
bundle.loadClass(className);
}
}
}
public static class BundleWrapper {
final String symbolicName;
final Version version;
public BundleWrapper(Bundle bundle) {
this.symbolicName = bundle.getSymbolicName();
this.version = bundle.getVersion();
}
public BundleWrapper(BundleRevision bundleRevision) {
this.symbolicName = bundleRevision.getSymbolicName();
this.version = bundleRevision.getVersion();
}
public BundleWrapper(Resource resource) {
this.symbolicName = ResolverUtil.getSymbolicName(resource);
this.version = ResolverUtil.getVersion(resource);
}
public String getSymbolicName() {
return symbolicName;
}
public Version getVersion() {
return version;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BundleWrapper that = (BundleWrapper) o;
return Objects.equals(symbolicName, that.symbolicName) &&
Objects.equals(version, that.version);
}
@Override
public int hashCode() {
return Objects.hash(symbolicName, version);
}
@Override
public String toString() {
return symbolicName + "/" + version;
}
}
}