/******************************************************************************* * Copyright (c) 2008, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VMware Inc. - initial contribution *******************************************************************************/ package org.eclipse.virgo.kernel.userregion.internal.quasi; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; 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.concurrent.atomic.AtomicLong; import org.eclipse.equinox.region.Region; import org.eclipse.equinox.region.RegionDigraph.FilteredRegion; import org.eclipse.equinox.region.RegionFilter; import org.eclipse.osgi.service.resolver.BundleDelta; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.osgi.service.resolver.BundleSpecification; import org.eclipse.osgi.service.resolver.ExportPackageDescription; import org.eclipse.osgi.service.resolver.HostSpecification; import org.eclipse.osgi.service.resolver.ImportPackageSpecification; import org.eclipse.osgi.service.resolver.ResolverError; import org.eclipse.osgi.service.resolver.State; import org.eclipse.osgi.service.resolver.StateDelta; import org.eclipse.osgi.service.resolver.StateObjectFactory; import org.eclipse.osgi.service.resolver.VersionConstraint; import org.eclipse.osgi.service.resolver.VersionRange; import org.eclipse.virgo.kernel.artifact.bundle.BundleBridge; import org.eclipse.virgo.kernel.osgi.framework.UnableToSatisfyBundleDependenciesException; import org.eclipse.virgo.kernel.osgi.framework.UnableToSatisfyDependenciesException; import org.eclipse.virgo.kernel.userregion.internal.quasi.ResolutionFailureDetective.ResolverErrorsHolder; import org.eclipse.virgo.medic.dump.DumpGenerator; import org.eclipse.virgo.repository.ArtifactDescriptor; import org.eclipse.virgo.repository.Attribute; import org.eclipse.virgo.repository.Query; import org.eclipse.virgo.repository.Repository; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Calculates the dependencies of a given set of {@link BundleDescription BundleDescriptions}. * <p /> * * <strong>Concurrent Semantics</strong><br /> * * Threadsafe. * */ public final class DependencyCalculator { // The following literal must match ResolutionDumpContributor.RESOLUTION_STATE_KEY from kernel core. private static final String RESOLUTION_STATE_KEY = "resolution.state"; private static final String REGION_LOCATION_DELIMITER = "@"; private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final ResolutionFailureDetective detective; private final AtomicLong nextBundleId = new AtomicLong(System.currentTimeMillis()); private final Repository repository; private final Object monitor = new Object(); private final StateObjectFactory stateObjectFactory; private final DumpGenerator dumpGenerator; private Region coregion; public DependencyCalculator(StateObjectFactory stateObjectFactory, ResolutionFailureDetective detective, Repository repository, BundleContext bundleContext) { this.repository = repository; this.detective = detective; this.stateObjectFactory = stateObjectFactory; this.dumpGenerator = bundleContext.getService(bundleContext.getServiceReference(DumpGenerator.class)); } /** * Calculates the dependencies of the supplied set of {@link BundleDescription bundles}. * <p/> * * Callers must supply a {@link State} against which dependency satisfaction is executed. The supplied State is * destructively modified during constraint satisfaction so it <strong>must</strong> not be the system state. * <p/> * * In a successful invocation, any new bundles that need to be installed are returned, and the supplied * <code>State</code> is transformed to reflect the newly resolved state of the supplied bundles. Callers can query * the <code>State</code> to find the fully wiring graph of the supplied bundles after successful constraint * satisfaction. * <p/> * * If diagnostics are forced, then an {@link UnableToSatisfyDependenciesException} is thrown if the constraints * cannot be satisfied. If diagnostics are not forced, then either an * <code>UnableToSatisfyDependenciesException</code> is thrown or the new bundles that need to be installed are * returned depending on whether cloning some bundles may improve the chances of satisfying the constraints. * * @param state the <code>State</code> to satisfy against. * @param coregion the coregion containing the side-state bundles * @param bundles the bundles to calculate dependencies for * @param disabledProvisioningBundles a subset of bundles which should not have their dependencies provisioned * @return an array of descriptions of bundles that need to be added to the state to satisfy constraints. * @throws BundleException * @throws UnableToSatisfyDependenciesException */ public BundleDescription[] calculateDependencies(State state, Region coregion, BundleDescription[] bundles, BundleDescription[] disabledProvisioningBundles) throws BundleException, UnableToSatisfyDependenciesException { this.logger.info("Calculating missing dependencies of bundle(s) '{}'", (Object[]) bundles); synchronized (this.monitor) { this.coregion = coregion; try { doSatisfyConstraints(bundles, state, disabledProvisioningBundles); StateDelta delta = state.resolve(bundles); for (BundleDescription description : bundles) { if (!description.isResolved()) { generateDump(state); ResolverErrorsHolder reh = new ResolverErrorsHolder(); String failure = this.detective.generateFailureDescription(state, description, reh); ResolverError[] resolverErrors = reh.getResolverErrors(); if (resolverErrors != null) { for (ResolverError resolverError : resolverErrors) { if (resolverError.getType() == ResolverError.IMPORT_PACKAGE_USES_CONFLICT) { VersionConstraint unsatisfiedConstraint = resolverError.getUnsatisfiedConstraint(); if (unsatisfiedConstraint instanceof ImportPackageSpecification) { ImportPackageSpecification importPackageSpecification = (ImportPackageSpecification) unsatisfiedConstraint; this.logger.debug("Uses conflict: package '{}' version '{}' bundle '{}' version '{}'", new Object[] { importPackageSpecification.getName(), importPackageSpecification.getVersionRange(), importPackageSpecification.getBundleSymbolicName(), importPackageSpecification.getBundleVersionRange() }); } } } } throw new UnableToSatisfyBundleDependenciesException(description.getSymbolicName(), description.getVersion(), failure, state, reh.getResolverErrors()); } } BundleDelta[] deltas = delta.getChanges(BundleDelta.ADDED, false); Set<BundleDescription> newBundles = new HashSet<BundleDescription>(); for (BundleDelta bundleDelta : deltas) { newBundles.add(bundleDelta.getBundle()); } Set<BundleDescription> dependenciesSet = getNewTransitiveDependencies(new HashSet<BundleDescription>(Arrays.asList(bundles)), newBundles); List<BundleDescription> dependencies = new ArrayList<BundleDescription>(dependenciesSet); this.logger.info("The dependencies of '{}' are '{}'", Arrays.toString(bundles), dependencies); Collections.sort(dependencies, new BundleDescriptionComparator()); BundleDescription[] dependencyDescriptions = dependencies.toArray(new BundleDescription[dependencies.size()]); return dependencyDescriptions; } finally { this.coregion = null; } } } private Set<BundleDescription> getNewTransitiveDependencies(Set<BundleDescription> dependingBundles, Collection<BundleDescription> newBundles) { Set<BundleDescription> transitiveDependencies = new HashSet<BundleDescription>(); while (!dependingBundles.isEmpty()) { Set<BundleDescription> newDependencies = new HashSet<BundleDescription>(); for (BundleDescription bundle : dependingBundles) { newDependencies.addAll(getNewImmediateDependencies(bundle, newBundles)); } // Next time round the loop, check new dependencies that we haven't already processed newDependencies.removeAll(transitiveDependencies); dependingBundles = newDependencies; transitiveDependencies.addAll(newDependencies); } return transitiveDependencies; } private Set<BundleDescription> getNewImmediateDependencies(BundleDescription bundle, Collection<BundleDescription> newBundles) { Set<BundleDescription> immediateDependencies = new HashSet<BundleDescription>(); immediateDependencies.addAll(Arrays.asList(bundle.getFragments())); immediateDependencies.addAll(Arrays.asList(bundle.getResolvedRequires())); immediateDependencies.addAll(getPackageProviders(bundle)); HostSpecification hostSpecification = bundle.getHost(); if (hostSpecification != null) { for (BundleDescription host : hostSpecification.getHosts()) { immediateDependencies.add(host); } } immediateDependencies.retainAll(newBundles); return immediateDependencies; } private Set<BundleDescription> getPackageProviders(BundleDescription bundleDescription) { Set<BundleDescription> packageProviders = new HashSet<BundleDescription>(); ExportPackageDescription[] resolvedImports = bundleDescription.getResolvedImports(); for (ExportPackageDescription resolvedImport : resolvedImports) { packageProviders.add(resolvedImport.getExporter()); } return packageProviders; } private void doSatisfyConstraints(BundleDescription description, State state, BundleDescription[] disabledProvisioningDescriptions) throws BundleException { doSatisfyConstraints(new BundleDescription[] { description }, state, disabledProvisioningDescriptions); } private void doSatisfyConstraints(BundleDescription[] descriptions, State state, BundleDescription[] disabledProvisioningDescriptions) throws BundleException { VersionConstraint[] unsatisfiedConstraints = findUnsatisfiedConstraints(descriptions, state); List<BundleDescription> constraintsSatisfiers = new ArrayList<BundleDescription>(); for (VersionConstraint versionConstraint : unsatisfiedConstraints) { BundleDescription unsatisfiedBundle = versionConstraint.getBundle(); boolean found = false; for (BundleDescription description : descriptions) { if (description == unsatisfiedBundle) { found = true; } } if (found) { if (provision(unsatisfiedBundle, disabledProvisioningDescriptions)) { if (versionConstraint instanceof ImportPackageSpecification) { satisfyImportPackage((ImportPackageSpecification) versionConstraint, state, constraintsSatisfiers); } else if (versionConstraint instanceof BundleSpecification) { satisfyRequireBundle(versionConstraint, state, constraintsSatisfiers); } else if (versionConstraint instanceof HostSpecification) { satisfyFragmentHost(versionConstraint, state, constraintsSatisfiers); } } } } for (BundleDescription description : descriptions) { if (provision(description, disabledProvisioningDescriptions)) { satisfyFragments(description, state, constraintsSatisfiers); } } Collections.sort(constraintsSatisfiers, new BundleDescriptionComparator()); for (BundleDescription constraintSatisfier : constraintsSatisfiers) { if (!isBundlePresentInState(constraintSatisfier.getName(), constraintSatisfier.getVersion(), state)) { state.addBundle(constraintSatisfier); this.coregion.addBundle(constraintSatisfier.getBundleId()); doSatisfyConstraints(constraintSatisfier, state, disabledProvisioningDescriptions); } } } private boolean provision(BundleDescription bundleDescription, BundleDescription[] disabledProvisioningDescriptions) { boolean provision = true; for (BundleDescription disabledProvisioningDescription : disabledProvisioningDescriptions) { if (disabledProvisioningDescription == bundleDescription) { provision = false; } } return provision; } private void satisfyFragments(BundleDescription description, State state, List<BundleDescription> constraintSatisfiers) throws BundleException { Set<? extends ArtifactDescriptor> fragmentArtefacts = this.repository.createQuery("type", BundleBridge.BRIDGE_TYPE).addFilter( "Fragment-Host", description.getSymbolicName()).run(); for (ArtifactDescriptor fragmentArtefact : fragmentArtefacts) { addBundle(fragmentArtefact, state, constraintSatisfiers); } } private void satisfyFragmentHost(VersionConstraint constraint, State state, List<BundleDescription> constraintSatisfiers) throws BundleException { Set<? extends ArtifactDescriptor> hostArtefacts = this.repository.createQuery("type", BundleBridge.BRIDGE_TYPE).addFilter( "Bundle-SymbolicName", constraint.getName()).run(); for (ArtifactDescriptor hostArtefact : hostArtefacts) { addBundle(hostArtefact, state, constraintSatisfiers); } } private void satisfyRequireBundle(VersionConstraint constraint, State state, List<BundleDescription> constraintSatisfiers) throws BundleException { Set<? extends ArtifactDescriptor> requiredBundleArtefacts = this.repository.createQuery("type", BundleBridge.BRIDGE_TYPE).addFilter( "Bundle-SymbolicName", constraint.getName()).run(); for (ArtifactDescriptor requiredBundleArtefact : requiredBundleArtefacts) { addBundle(requiredBundleArtefact, state, constraintSatisfiers); } } private void satisfyImportPackage(ImportPackageSpecification constraint, State state, List<BundleDescription> constraintSatisfiers) throws BundleException { VersionRange packageVersionRange = constraint.getVersionRange(); Query query = this.repository.createQuery("type", BundleBridge.BRIDGE_TYPE); boolean loosePackageVersionRange = false; if (packageVersionRange != null && packageVersionRange.getMaximum().equals(packageVersionRange.getMinimum())) { Map<String, Set<String>> properties = new HashMap<String, Set<String>>(); properties.put("version", new HashSet<String>(Arrays.asList(packageVersionRange.getMaximum().toString()))); query.addFilter("Export-Package", constraint.getName(), properties); } else { query.addFilter("Export-Package", constraint.getName()); loosePackageVersionRange = packageVersionRange != null; } String bundleSymbolicName = constraint.getBundleSymbolicName(); if (bundleSymbolicName != null) { query.addFilter("Bundle-SymbolicName", bundleSymbolicName); } VersionRange bundleVersionRange = constraint.getBundleVersionRange(); boolean looseBundleVersionRange = false; if (bundleVersionRange != null && bundleVersionRange.getMaximum().equals(bundleVersionRange.getMinimum())) { query.addFilter("Bundle-Version", bundleVersionRange.getMaximum().toString()); } else { looseBundleVersionRange = bundleVersionRange != null; } Set<? extends ArtifactDescriptor> packageExportingArtefacts = query.run(); for (ArtifactDescriptor packageExportingArtefact : packageExportingArtefacts) { if ((!loosePackageVersionRange || packageVersionInRange(packageExportingArtefact, packageVersionRange, constraint.getName())) && (!looseBundleVersionRange || bundleVersionInRange(packageExportingArtefact, bundleVersionRange))) { addBundle(packageExportingArtefact, state, constraintSatisfiers); } } } private boolean packageVersionInRange(ArtifactDescriptor packageExportingArtefact, VersionRange packageVersionRange, String packageName) { for (Attribute attribute : packageExportingArtefact.getAttribute("Export-Package")) { if (attribute.getValue().equals(packageName)) { Set<String> versions = attribute.getProperties().get("version"); Version version = new org.osgi.framework.Version(versions == null || versions.isEmpty() ? "0" : versions.iterator().next()); if (packageVersionRange.isIncluded(version)) { return true; } } } return false; } private boolean bundleVersionInRange(ArtifactDescriptor packageExportingArtefact, VersionRange bundleVersionRange) { return bundleVersionRange.isIncluded(packageExportingArtefact.getVersion()); } private void addBundle(ArtifactDescriptor artefact, State state, List<BundleDescription> constraintSatisfiers) throws BundleException { if (!isBundlePresentInState(artefact.getName(), artefact.getVersion(), state)) { BundleDescription description = createBundleDescription(artefact, state); constraintSatisfiers.add(description); } } private boolean isBundlePresentInState(String bundleSymbolicName, Version version, State state) { BundleDescription[] bundleDescriptions = state.getBundles(bundleSymbolicName); for (BundleDescription bundleDescription : bundleDescriptions) { if (bundleDescription.getVersion().equals(version)) { long bundleId = bundleDescription.getBundleId(); if (bundleId == 0L || this.coregion.contains(bundleId)) { return true; } // XXX Refactoring required here. This temporary code only traverses the coregion and user region. Set<FilteredRegion> edges = this.coregion.getEdges(); Iterator<FilteredRegion> iterator = edges.iterator(); // Bug 377392: cope with the unexpected case of a coregion with no edges. if (iterator.hasNext()) { FilteredRegion edge = iterator.next(); Region userRegion = edge.getRegion(); RegionFilter filter = edge.getFilter(); if (filter.isAllowed(bundleDescription) && userRegion.contains(bundleId)) { return true; } } } } return false; } private BundleDescription createBundleDescription(ArtifactDescriptor artifact, State state) throws BundleException { Dictionary<String, String> manifest = BundleBridge.convertToDictionary(artifact); try { URI uri = artifact.getUri(); String installLocation = "file".equals(uri.getScheme()) ? new File(uri).getAbsolutePath() : uri.toString(); BundleDescription bundleDescription = this.stateObjectFactory.createBundleDescription(state, manifest, this.coregion.getName() + REGION_LOCATION_DELIMITER + installLocation, this.nextBundleId.getAndIncrement()); this.coregion.addBundle(bundleDescription.getBundleId()); return bundleDescription; } catch (RuntimeException e) { throw new BundleException("Unable to read bundle at '" + artifact.getUri() + "'", e); } catch (BundleException be) { throw new BundleException("Failed to create BundleDescriptor for artifact at '" + artifact.getUri() + "'", be); } } private VersionConstraint[] findUnsatisfiedConstraints(BundleDescription[] bundles, State state) { return state.getStateHelper().getUnsatisfiedLeaves(bundles); } private void generateDump(State state) { Map<String, Object> context = new HashMap<String, Object>(); context.put(RESOLUTION_STATE_KEY, state); this.dumpGenerator.generateDump("resolutionFailure", context); } public long getNextBundleId() { return this.nextBundleId.getAndIncrement(); } }