/******************************************************************************* * 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.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.equinox.region.Region; import org.eclipse.equinox.region.RegionDigraph; import org.eclipse.equinox.region.RegionFilter; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.osgi.service.resolver.ImportPackageSpecification; import org.eclipse.osgi.service.resolver.PlatformAdmin; import org.eclipse.osgi.service.resolver.ResolverError; import org.eclipse.osgi.service.resolver.State; import org.eclipse.osgi.service.resolver.StateObjectFactory; import org.eclipse.osgi.service.resolver.VersionConstraint; import org.eclipse.virgo.kernel.artifact.plan.PlanDescriptor.Provisioning; import org.eclipse.virgo.kernel.osgi.framework.ManifestTransformer; import org.eclipse.virgo.kernel.osgi.framework.UnableToSatisfyDependenciesException; import org.eclipse.virgo.kernel.osgi.quasi.QuasiBundle; import org.eclipse.virgo.kernel.osgi.quasi.QuasiFramework; import org.eclipse.virgo.kernel.osgi.quasi.QuasiResolutionFailure; import org.eclipse.virgo.kernel.userregion.internal.equinox.TransformedManifestProvidingBundleFileWrapper; import org.eclipse.virgo.kernel.userregion.internal.quasi.ResolutionFailureDetective.ResolverErrorsHolder; import org.eclipse.virgo.nano.core.FatalKernelException; import org.eclipse.virgo.repository.Repository; import org.eclipse.virgo.util.common.StringUtils; import org.eclipse.virgo.util.osgi.manifest.BundleManifest; import org.eclipse.virgo.util.osgi.manifest.VersionRange; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.hooks.resolver.ResolverHookFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link StandardQuasiFramework} is the default implementation of {@link QuasiFramework}. * <p /> * * <strong>Concurrent Semantics</strong><br /> * * This class is thread safe. * */ final class StandardQuasiFramework implements QuasiFramework { private static final BundleDescription[] EMPTY_BUNDLE_DESCRIPTION_ARRAY = new BundleDescription[0]; private final RegionFilter TOP; private static final String REGION_LOCATION_DELIMITER = "@"; private static final String COREGION_SUFFIX = ".coregion"; private static final String REFERENCE_SCHEME = "reference:"; private static final String FILE_SCHEME = "file:"; private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Object monitor = new Object(); private final State state; private final StateObjectFactory stateObjectFactory; /* * Track the bundles which are explicitly installed. These are input to the resolve method. */ private final List<StandardQuasiBundle> installedQuasiBundles = new ArrayList<StandardQuasiBundle>(); private volatile BundleDescription[] otherBundles; private final ResolutionFailureDetective detective; private final BundleContext bundleContext; private final DependencyCalculator dependencyCalculator; private final TransformedManifestProvidingBundleFileWrapper bundleTransformationHandler; private final RegionDigraph regionDigraph; private Region coregion; private final Region userRegion; StandardQuasiFramework(BundleContext bundleContext, State state, PlatformAdmin platformAdmin, ResolutionFailureDetective detective, Repository repository, TransformedManifestProvidingBundleFileWrapper bundleTransformationHandler, RegionDigraph regionDigraph) { TOP = regionDigraph.createRegionFilterBuilder().allowAll(RegionFilter.VISIBLE_ALL_NAMESPACE).build(); this.bundleContext = bundleContext; this.state = state; this.stateObjectFactory = platformAdmin.getFactory(); this.detective = detective; this.bundleTransformationHandler = bundleTransformationHandler; this.regionDigraph = regionDigraph; this.userRegion = regionDigraph.getRegion("org.eclipse.virgo.region.user"); this.coregion = regionDigraph.getRegion(this.userRegion.getName() + COREGION_SUFFIX); setResolverHookFactory(); this.dependencyCalculator = new DependencyCalculator(platformAdmin.getFactory(), this.detective, repository, this.bundleContext); } private void setResolverHookFactory() { /* * Create a resolver hook factory for the region digraph. If the region digraph is live, this will create a hook * factory equivalent to the live hook factory. If the region digraph is disconnected (a reconstituted copy of a * live region digraph), this will produce a hook factory independent of the live hook factory. */ ResolverHookFactory resolverHookFactory = this.regionDigraph.getResolverHookFactory(); this.state.setResolverHookFactory(resolverHookFactory); } /** * {@inheritDoc} */ public QuasiBundle install(URI location, BundleManifest bundleManifest) throws BundleException { synchronized (this.monitor) { createCoregionIfNecessary(); StandardQuasiBundle qb = doInstall(location, bundleManifest); this.installedQuasiBundles.add(qb); return qb; } } private void createCoregionIfNecessary() { synchronized (this.monitor) { if (this.coregion == null) { try { this.coregion = this.regionDigraph.createRegion(this.userRegion.getName() + COREGION_SUFFIX); this.userRegion.connectRegion(this.coregion, TOP); this.coregion.connectRegion(this.userRegion, TOP); } catch (BundleException e) { // should never happen throw new FatalKernelException("Failed to create coregion", e); } } } } private StandardQuasiBundle doInstall(URI location, BundleManifest bundleManifest) throws BundleException { try { Dictionary<String, String> manifest = bundleManifest.toDictionary(); String installLocation = "file".equals(location.getScheme()) ? new File(location).getAbsolutePath() : location.toString(); BundleDescription bundleDescription = this.stateObjectFactory.createBundleDescription(this.state, manifest, this.coregion.getName() + REGION_LOCATION_DELIMITER + installLocation, nextBundleId()); this.state.addBundle(bundleDescription); this.coregion.addBundle(bundleDescription.getBundleId()); return new StandardQuasiBundle(bundleDescription, bundleManifest, this.regionDigraph.getRegion(bundleDescription.getBundleId())); } catch (RuntimeException e) { throw new BundleException("Unable to read bundle at '" + location + "'", e); } } /** * @return */ private long nextBundleId() { return this.dependencyCalculator.getNextBundleId(); } /** * {@inheritDoc} */ public List<QuasiBundle> getBundles() { BundleDescription[] bundleDescriptions = this.state.getBundles(); List<QuasiBundle> result = new ArrayList<QuasiBundle>(); QuasiBundle quasiBundle; for (BundleDescription bundleDescription : bundleDescriptions) { quasiBundle = new StandardQuasiBundle(bundleDescription, null, this.regionDigraph.getRegion(bundleDescription.getBundleId())); result.add(quasiBundle); } return Collections.unmodifiableList(result); } /** * {@inheritDoc} */ public QuasiBundle getBundle(long bundleId) { QuasiBundle quasiBundle = null; BundleDescription bundleDescription = this.state.getBundle(bundleId); if (bundleDescription != null) { quasiBundle = new StandardQuasiBundle(bundleDescription, null, this.regionDigraph.getRegion(bundleId)); } return quasiBundle; } /** * {@inheritDoc} */ public Set<Region> getRegions(){ return this.regionDigraph.getRegions(); } /** * {@inheritDoc} */ public List<QuasiResolutionFailure> resolve() { synchronized (this.monitor) { BundleDescription[] bundles = getBundleDescriptionArray(); BundleDescription[] disabledProvisioningBundles = getDisabledProvisioningBundleDescriptionArray(); BundleDescription[] dependencies = getDependencies(bundles, disabledProvisioningBundles); this.otherBundles = dependencies; List<QuasiResolutionFailure> failures = getFailures(); if (!failures.isEmpty()) { this.otherBundles = null; } return failures; } } /** * {@inheritDoc} */ public List<QuasiResolutionFailure> diagnose(long bundleId) { BundleDescription bundleDescription = this.state.getBundle(bundleId); ResolverErrorsHolder reh = new ResolverErrorsHolder(); String failureDescription = this.detective.generateFailureDescription(this.state, bundleDescription, reh); return this.processResolverErrors(reh.getResolverErrors(), new StandardQuasiBundle(bundleDescription, null, this.regionDigraph.getRegion(bundleId)), failureDescription); } private BundleDescription[] getDependencies(BundleDescription[] bundles, BundleDescription[] disabledProvisioningBundles) { createCoregionIfNecessary(); try { return this.dependencyCalculator.calculateDependencies(this.state, this.coregion, bundles, disabledProvisioningBundles); } catch (BundleException e) { return EMPTY_BUNDLE_DESCRIPTION_ARRAY; } catch (UnableToSatisfyDependenciesException utsde) { return EMPTY_BUNDLE_DESCRIPTION_ARRAY; } } private List<QuasiResolutionFailure> getFailures() { List<QuasiResolutionFailure> failures = new ArrayList<QuasiResolutionFailure>(); ResolverErrorsHolder reh; String failureDescription; for (StandardQuasiBundle quasiBundle : this.installedQuasiBundles) { if (!quasiBundle.isResolved()) { reh = new ResolverErrorsHolder(); failureDescription = this.detective.generateFailureDescription(this.state, quasiBundle.getBundleDescription(), reh); failures.addAll(this.processResolverErrors(reh.getResolverErrors(), quasiBundle, failureDescription)); } } return failures; } private List<QuasiResolutionFailure> processResolverErrors(ResolverError[] resolverErrors, QuasiBundle quasiBundle, String failureDescription) { List<QuasiResolutionFailure> processedResolverErrors = new ArrayList<QuasiResolutionFailure>(); boolean added = false; if (resolverErrors != null) { for (ResolverError resolverError : resolverErrors) { if (resolverError.getType() == ResolverError.IMPORT_PACKAGE_USES_CONFLICT) { VersionConstraint unsatisfiedConstraint = resolverError.getUnsatisfiedConstraint(); if (unsatisfiedConstraint instanceof ImportPackageSpecification) { processedResolverErrors.add(createPackagesUsesResolutionFailure(quasiBundle, failureDescription, unsatisfiedConstraint)); added = true; } } else if (resolverError.getType() == ResolverError.MISSING_IMPORT_PACKAGE) { VersionConstraint unsatisfiedConstraint = resolverError.getUnsatisfiedConstraint(); if (unsatisfiedConstraint instanceof ImportPackageSpecification) { processedResolverErrors.add(createPackageResolutionFailure(quasiBundle, failureDescription, unsatisfiedConstraint)); added = true; } } } } if (!added) { processedResolverErrors.add(new GenericQuasiResolutionFailure(quasiBundle, failureDescription)); } return processedResolverErrors; } private PackageQuasiResolutionFailure createPackageResolutionFailure(QuasiBundle quasiBundle, String failureDescription, VersionConstraint unsatisfiedConstraint) { ImportPackageSpecification importPackageSpecification = (ImportPackageSpecification) unsatisfiedConstraint; String pkgName = importPackageSpecification.getName(); VersionRange pkgVersionRange = convertVersionRange(importPackageSpecification.getVersionRange()); String bundleSymbolicName = importPackageSpecification.getBundleSymbolicName(); VersionRange bundleVersionRange = convertVersionRange(importPackageSpecification.getBundleVersionRange()); long bundleId = importPackageSpecification.getBundle().getBundleId(); this.logger.debug("Missing import: package '{}' version '{}' bundle '{}' version '{}' id '{}'", new Object[] { pkgName, pkgVersionRange, bundleSymbolicName, bundleVersionRange, bundleId }); return new PackageQuasiResolutionFailure(failureDescription, quasiBundle, pkgName, pkgVersionRange, bundleSymbolicName, bundleVersionRange); } private PackageUsesQuasiResolutionFailure createPackagesUsesResolutionFailure(QuasiBundle quasiBundle, String failureDescription, VersionConstraint unsatisfiedConstraint) { ImportPackageSpecification importPackageSpecification = (ImportPackageSpecification) unsatisfiedConstraint; String pkgName = importPackageSpecification.getName(); VersionRange pkgVersionRange = convertVersionRange(importPackageSpecification.getVersionRange()); String bundleSymbolicName = importPackageSpecification.getBundleSymbolicName(); VersionRange bundleVersionRange = convertVersionRange(importPackageSpecification.getBundleVersionRange()); long bundleId = importPackageSpecification.getBundle().getBundleId(); this.logger.debug("Uses conflict: package '{}' version '{}' bundle '{}' version '{}' id '{}'", new Object[] { pkgName, pkgVersionRange, bundleSymbolicName, bundleVersionRange, bundleId }); return new PackageUsesQuasiResolutionFailure(failureDescription, quasiBundle, pkgName, pkgVersionRange, bundleSymbolicName, bundleVersionRange); } private static VersionRange convertVersionRange(org.eclipse.osgi.service.resolver.VersionRange versionRange) { return new VersionRange(versionRange.toString()); } private BundleDescription[] getBundleDescriptionArray() { BundleDescription[] bd; int n = this.installedQuasiBundles.size(); bd = new BundleDescription[n]; for (int i = 0; i < n; i++) { bd[i] = this.installedQuasiBundles.get(i).getBundleDescription(); } return bd; } private BundleDescription[] getDisabledProvisioningBundleDescriptionArray() { ArrayList<BundleDescription> disabledProvisioningBundleDescriptions = new ArrayList<BundleDescription>(); int n = this.installedQuasiBundles.size(); for (int i = 0; i < n; i++) { StandardQuasiBundle quasiBundle = this.installedQuasiBundles.get(i); if (quasiBundle.getProvisioning() == Provisioning.DISABLED) { disabledProvisioningBundleDescriptions.add(quasiBundle.getBundleDescription()); } } return disabledProvisioningBundleDescriptions.toArray(EMPTY_BUNDLE_DESCRIPTION_ARRAY); } /** * {@inheritDoc} */ @Override public void commit() throws BundleException { synchronized (this.monitor) { if (this.otherBundles == null) { List<QuasiResolutionFailure> failures = resolve(); if (!failures.isEmpty()) { throw new BundleException("Commit resolution failed: '" + failures.toString() + "'"); } } else { try { Set<Long> installedQuasiBundles = installQuasiBundles(); List<Bundle> installedDependencies = installOtherBundles(installedQuasiBundles); startBundles(installedDependencies); } catch (BundleException e) { uninstallQuasiBundles(); throw e; } } } } private void startBundles(List<Bundle> bundles) throws BundleException { for (Bundle bundle : bundles) { startBundle(bundle); } } private void startBundle(Bundle bundle) throws BundleException { String fragmentHostHeader = (String) bundle.getHeaders().get(Constants.FRAGMENT_HOST); if (!StringUtils.hasText(fragmentHostHeader)) { try { bundle.start(); } catch (BundleException be) { throw new BundleException("Failed to start bundle '" + bundle.getSymbolicName() + "' version '" + bundle.getVersion() + "'", be); } } } private List<Bundle> installOtherBundles(Set<Long> installedQuasiBundles) throws BundleException { List<Bundle> installedBundles = new ArrayList<Bundle>(); for (BundleDescription otherBundle : otherBundles) { if (!installedQuasiBundles.contains(otherBundle.getBundleId())) { try { Bundle bundle = installBundleDescription(otherBundle); installedBundles.add(bundle); } catch (BundleException e) { for (Bundle bundle : installedBundles) { try { bundle.uninstall(); } catch (BundleException be) { this.logger.error("Uninstall of '{}' failed", be, bundle); } } throw e; } } } return installedBundles; } private Set<Long> installQuasiBundles() throws BundleException { Set<Long> installed = new HashSet<Long>(); for (StandardQuasiBundle quasiBundle : this.installedQuasiBundles) { BundleDescription description = quasiBundle.getBundleDescription(); String location = description.getLocation(); ManifestTransformer manifestTransformer = new QuasiManifestTransformer(quasiBundle.getBundleManifest()); this.bundleTransformationHandler.pushManifestTransformer(manifestTransformer); try { URI locationUri = new File(stripRegionTag(location)).toURI(); Bundle bundle = doInstallBundleInternal(locationUri.toString()); quasiBundle.setBundle(bundle); installed.add(description.getBundleId()); } finally { this.bundleTransformationHandler.popManifestTransformer(); } } return installed; } private String stripRegionTag(String location) { int atPos = location.indexOf(REGION_LOCATION_DELIMITER); if (atPos != -1) { return location.substring(atPos + 1); } return location; } private static final class QuasiManifestTransformer implements ManifestTransformer { private final BundleManifest bundleManifest; public QuasiManifestTransformer(BundleManifest bundleManifest) { this.bundleManifest = bundleManifest; } /** * {@inheritDoc} */ public BundleManifest transform(BundleManifest bundleManifest) { return this.bundleManifest; } } private Bundle installBundleDescription(BundleDescription description) throws BundleException { String location = stripRegionTag(description.getLocation()); String installLocation = location.startsWith("http:") ? location : new File(location).toURI().toString(); return doInstallBundleInternal(installLocation); } private Bundle doInstallBundleInternal(String location) throws BundleException { return this.userRegion.installBundle(location, openBundleStream(location)); } private InputStream openBundleStream(String location) throws BundleException { String absoluteBundleUriString = getAbsoluteUriString(location); try { // Use the reference: scheme to obtain an InputStream for either a file or a directory. return new URL(REFERENCE_SCHEME + absoluteBundleUriString).openStream(); } catch (MalformedURLException e) { throw new BundleException("Invalid bundle URI '" + absoluteBundleUriString + "'", e); } catch (IOException e) { throw new BundleException("Invalid bundle at URI '" + absoluteBundleUriString + "'", e); } } private String getAbsoluteUriString(String bundleUriString) throws BundleException { if (!bundleUriString.startsWith(FILE_SCHEME)) { throw new BundleException("'" + bundleUriString + "' which did not start with '" + FILE_SCHEME + "'"); } String filePath = bundleUriString.substring(FILE_SCHEME.length()); return FILE_SCHEME + new File(filePath).getAbsolutePath(); } private void uninstallQuasiBundles() { for (StandardQuasiBundle quasiBundle : this.installedQuasiBundles) { quasiBundle.uninstall(); } } @Override public void destroy() { Region coregionCopy; synchronized (this.monitor) { coregionCopy = this.coregion; this.coregion = null; } if (coregionCopy != null) { this.regionDigraph.removeRegion(coregionCopy); } } }