/******************************************************************************* * 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.install.artifact.internal.scoping; import java.util.HashMap; import java.util.List; import java.util.Map; import org.osgi.framework.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.eclipse.virgo.util.osgi.manifest.VersionRange; import org.eclipse.virgo.util.osgi.manifest.BundleManifest; import org.eclipse.virgo.util.osgi.manifest.BundleManifestFactory; import org.eclipse.virgo.util.osgi.manifest.BundleSymbolicName; import org.eclipse.virgo.util.osgi.manifest.DynamicImportPackage; import org.eclipse.virgo.util.osgi.manifest.DynamicallyImportedPackage; import org.eclipse.virgo.util.osgi.manifest.ExportedPackage; import org.eclipse.virgo.util.osgi.manifest.FragmentHost; import org.eclipse.virgo.util.osgi.manifest.ImportedBundle; import org.eclipse.virgo.util.osgi.manifest.ImportedPackage; import org.eclipse.virgo.util.osgi.manifest.RequiredBundle; /** * {@link Scoper} is a utility class for scoping a set of bundles. * <p/> * * <strong>Concurrent Semantics</strong><br /> * * This class is not thread safe. * */ public class Scoper { private static final String BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME = "bundle-symbolic-name"; public static final String SCOPE_SEPARATOR = "-"; private static final int BUNDLE_MANIFEST_VERSION_FOR_OSGI_R4 = 2; private static final String SCOPING_ATTRIBUTE_NAME = "module_scope"; private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final List<BundleManifest> bundleManifests; private final String scopeName; private final String scopePrefix; // Map of unscoped package name to package version for all the packages exported by the bundles. private final Map<String, Version> exportedPackages = new HashMap<String, Version>(); // Map of unscoped bundle symbolic name to bundle version for all the bundles. private final Map<String, Version> bundles = new HashMap<String, Version>(); /** * @param bundleManifests the manifests of the bundles to be scoped * @param scopeName the name of the scope to be created */ public Scoper(List<BundleManifest> bundleManifests, String scopeName) { this.bundleManifests = bundleManifests; this.scopeName = scopeName; this.scopePrefix = scopeName + "-"; } /** * Scope the bundles by transforming their metadata. This involves adding a mandatory matching attribute to exports * and any corresponding imports and adding a prefix to each bundle's bundle symbolic name and any corresponding * require bundle statements and matching attributes. The value of the mandatory matching attribute and the prefix * are uniquely determined by the application name and version. * * Scoping also checks for ambiguities which could lead to unexpected wirings and throws an exception if these * checks fail. Specifically, each package exported by the bundles must be exported only once and no two bundles may * have the same bundle symbolic name. * @throws UnsupportedBundleManifestVersionException * @throws DuplicateExportException * @throws DuplicateBundleSymbolicNameException */ public void scope() throws UnsupportedBundleManifestVersionException, DuplicateExportException, DuplicateBundleSymbolicNameException { scopeReferents(); scopeReferences(); } /** * Determine which packages are exported by the bundles and scope these exports and determine the bundles' symbolic * names and scope them. */ private void scopeReferents() throws UnsupportedBundleManifestVersionException, DuplicateExportException, DuplicateBundleSymbolicNameException { for (BundleManifest bundleManifest : this.bundleManifests) { scopeBundleReferents(bundleManifest, true); } } /** * Scope the referents of the given bundle. * * @param bundleManifest * @throws UnsupportedBundleManifestVersionException * @throws DuplicateExportException * @throws DuplicateBundleSymbolicNameException */ private void scopeBundleReferents(BundleManifest bundleManifest, boolean allowDuplicates) throws UnsupportedBundleManifestVersionException, DuplicateExportException, DuplicateBundleSymbolicNameException { logger.debug("Bundle manifest before scoping:\n{}", bundleManifest); // OSGi R4 features are essential for scoping. if (bundleManifest.getBundleManifestVersion() < BUNDLE_MANIFEST_VERSION_FOR_OSGI_R4) { throw new UnsupportedBundleManifestVersionException(); } for (ExportedPackage exportedPackage : bundleManifest.getExportPackage().getExportedPackages()) { scopeExportedPackage(exportedPackage, allowDuplicates); } setModuleScope(bundleManifest); try { String symbolicName = bundleManifest.getBundleSymbolicName().getSymbolicName(); if (symbolicName.startsWith(this.scopeName)) { symbolicName = symbolicName.substring(this.scopeName.length() + 1); } this.bundles.put(symbolicName, bundleManifest.getBundleVersion()); } catch (StringIndexOutOfBoundsException e) { throw e; } } private void setModuleScope(BundleManifest bundleManifest) { bundleManifest.setModuleScope(this.scopeName); } /** * Scope the given package export. * * @param exportedPackage the package export to be scoped * @param allowDuplicates true if and only if duplicated exports should be allowed */ private void scopeExportedPackage(ExportedPackage exportedPackage, boolean allowDuplicates) throws DuplicateExportException, UnsupportedBundleManifestVersionException { exportedPackage.getAttributes().put(SCOPING_ATTRIBUTE_NAME, this.scopeName); exportedPackage.getMandatory().add(SCOPING_ATTRIBUTE_NAME); Version packageVersion = exportedPackage.getVersion(); if (this.exportedPackages.containsKey(exportedPackage.getPackageName())) { if (!allowDuplicates) { diagnoseDuplicateExport(exportedPackage.getPackageName()); } } else { this.exportedPackages.put(exportedPackage.getPackageName(), packageVersion); } } /** * @param packageName */ private void diagnoseDuplicateExport(String packageName) throws DuplicateExportException, UnsupportedBundleManifestVersionException { StringBuffer exporters = new StringBuffer(); boolean first = true; for (BundleManifest bundleManifest : this.bundleManifests) { // OSGi R4 features are essential for scoping. if (bundleManifest.getBundleManifestVersion() < BUNDLE_MANIFEST_VERSION_FOR_OSGI_R4) { throw new UnsupportedBundleManifestVersionException(); } for (ExportedPackage exportedPackage : bundleManifest.getExportPackage().getExportedPackages()) { if (packageName.equals(exportedPackage.getPackageName())) { if (!first) { exporters.append(", "); } first = false; exporters.append(getUnscopedSymbolicName(bundleManifest)); exporters.append(" "); exporters.append(bundleManifest.getBundleVersion()); } } } throw new DuplicateExportException(packageName, exporters.toString()); } /** * Get the bundle symbolic name of the given bundle manifest prior to scoping. * * @param bundleManifest the bundle manifest * @return the unscoped bundle symbolic name */ public static String getUnscopedSymbolicName(BundleManifest bundleManifest) { String symbolicName = null; BundleSymbolicName bundleSymbolicName = bundleManifest.getBundleSymbolicName(); if (bundleSymbolicName != null) { symbolicName = bundleSymbolicName.getSymbolicName(); String moduleScope = bundleManifest.getModuleScope(); if (moduleScope != null) { String scopeName = moduleScope + SCOPE_SEPARATOR; if (symbolicName.startsWith(scopeName)) { symbolicName = symbolicName.substring(scopeName.length()); } } } return symbolicName; } public String getUnscopedSymbolicName(String bundleSymbolicName) { String symbolicName = null; if (bundleSymbolicName != null) { symbolicName = bundleSymbolicName; if (symbolicName.startsWith(this.scopePrefix)) { symbolicName = symbolicName.substring(this.scopePrefix.length()); } } return symbolicName; } public String getScopedSymbolicName(String bundleSymbolicName) { String symbolicName = getUnscopedSymbolicName(bundleSymbolicName); if (this.bundles.containsKey(symbolicName)) { return this.scopePrefix + symbolicName; } return bundleSymbolicName; } /** * Scope the corresponding imports and dynamic imports and scope the corresponding require-bundle and imports and * dynamic imports. */ private void scopeReferences() { for (BundleManifest bundleManifest : this.bundleManifests) { scopeBundleReferences(bundleManifest); } } /** * Scope the references of the given bundle. * * @param bundleManifest */ private void scopeBundleReferences(BundleManifest bundleManifest) { for (ImportedPackage importedPackage : bundleManifest.getImportPackage().getImportedPackages()) { scopeImportedPackage(importedPackage); } scopeDynamicImports(bundleManifest); scopeImportBundle(bundleManifest); scopeRequireBundle(bundleManifest); scopeFragmentHost(bundleManifest); logger.debug("Bundle manifest after scoping:\n{}", bundleManifest); } private void scopeDynamicImports(BundleManifest bundleManifest) { DynamicImportPackage unscopedList = BundleManifestFactory.createBundleManifest().getDynamicImportPackage(); List<DynamicallyImportedPackage> dynamicallyImportedPackages = bundleManifest.getDynamicImportPackage().getDynamicallyImportedPackages(); for (DynamicallyImportedPackage dynamicallyImportedPackage : dynamicallyImportedPackages) { scopeDynamicallyImportedPackage(dynamicallyImportedPackage); addUnscopedDynamicallyImportedPackage(unscopedList, dynamicallyImportedPackage); } dynamicallyImportedPackages.addAll(unscopedList.getDynamicallyImportedPackages()); } private void addUnscopedDynamicallyImportedPackage(DynamicImportPackage unscopedList, DynamicallyImportedPackage dynamicallyImportedPackage) { unscopedList.addDynamicallyImportedPackage(dynamicallyImportedPackage.getPackageName()); List<DynamicallyImportedPackage> unscopedDynamicallyImportedPackages = unscopedList.getDynamicallyImportedPackages(); DynamicallyImportedPackage unscopedDIP = unscopedDynamicallyImportedPackages.get(unscopedDynamicallyImportedPackages.size()-1); Map<String, String> attributes = unscopedDIP.getAttributes(); attributes.putAll(dynamicallyImportedPackage.getAttributes()); attributes.remove(SCOPING_ATTRIBUTE_NAME); } /** * Scope the given package import. * * @param importedPackage the package import to be scoped */ private void scopeImportedPackage(ImportedPackage importedPackage) { Version exportedVersion = this.exportedPackages.get(importedPackage.getPackageName()); if (exportedVersion != null) { VersionRange importVersionRange = importedPackage.getVersion(); if (importVersionRange.includes(exportedVersion)) { importedPackage.getAttributes().put(SCOPING_ATTRIBUTE_NAME, this.scopeName); if (importedPackage.getAttributes().containsKey(BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME)) { String symbolicName = importedPackage.getAttributes().get(BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME); if (this.bundles.containsKey(symbolicName)) { importedPackage.getAttributes().put(BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME, this.scopePrefix + symbolicName); } } } else { logger.warn( "Import of package '{}' was not scoped to scope '{}' as its version range did not include the version of the scoped export of the package", importedPackage.getPackageName(), this.scopeName); } } } private void scopeDynamicallyImportedPackage(DynamicallyImportedPackage dynamicallyImportedPackage) { dynamicallyImportedPackage.getAttributes().put(SCOPING_ATTRIBUTE_NAME, this.scopeName); if (dynamicallyImportedPackage.getAttributes().containsKey(BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME)) { String symbolicName = dynamicallyImportedPackage.getAttributes().get(BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME); if (this.bundles.containsKey(symbolicName)) { dynamicallyImportedPackage.getAttributes().put(BUNDLE_SYMBOLIC_NAME_ATTRIBUTE_NAME, this.scopePrefix + symbolicName); } } } /** * Scope the given require bundle. * * @param bundleManifest the bundle manifest to be scoped */ private void scopeRequireBundle(BundleManifest bundleManifest) { List<RequiredBundle> requiredBundles = bundleManifest.getRequireBundle().getRequiredBundles(); for (RequiredBundle requiredBundle : requiredBundles) { String requiredBundleSymbolicName = requiredBundle.getBundleSymbolicName(); if (this.bundles.containsKey(requiredBundleSymbolicName)) { Version version = this.bundles.get(requiredBundleSymbolicName); VersionRange requiredVersionRange = requiredBundle.getBundleVersion(); if (requiredVersionRange.includes(version)) { requiredBundle.setBundleSymbolicName(this.scopePrefix + requiredBundleSymbolicName); } } } } /** * Scope the manifest's Import-Bundle header * * @param bundleManifest the bundle manifest to be scoped */ private void scopeImportBundle(BundleManifest bundleManifest) { List<ImportedBundle> importedBundles = bundleManifest.getImportBundle().getImportedBundles(); for (ImportedBundle importedBundle : importedBundles) { String importedBundleSymbolicName = importedBundle.getBundleSymbolicName(); if (this.bundles.containsKey(importedBundleSymbolicName)) { Version version = this.bundles.get(importedBundleSymbolicName); VersionRange versionRange = importedBundle.getVersion(); if (versionRange.includes(version)) { importedBundle.setBundleSymbolicName(this.scopePrefix + importedBundleSymbolicName); } } } } /** * Scope any reference to one of the scoped bundles by a fragment host header. * * @param bundleManifest the bundle manifest to be scoped for fragment host */ private void scopeFragmentHost(BundleManifest bundleManifest) { FragmentHost fragmentHost = bundleManifest.getFragmentHost(); String bundleSymbolicName = fragmentHost.getBundleSymbolicName(); if (bundleSymbolicName != null) { if (this.bundles.containsKey(bundleSymbolicName)) { fragmentHost.setBundleSymbolicName(this.scopePrefix + bundleSymbolicName); } } } public static class DuplicateExportException extends Exception { private static final long serialVersionUID = -6672058951941449763L; private final String packageName; private final String exporters; private DuplicateExportException(String packageName, String exporters) { this.packageName = packageName; this.exporters = exporters; } public String getPackageName() { return this.packageName; } public String getExporters() { return this.exporters; } } public static class UnsupportedBundleManifestVersionException extends Exception { private static final long serialVersionUID = -282020071571817876L; UnsupportedBundleManifestVersionException() { super("Cannot scope a bundle which does not specify a bundle manifest version of at least " + BUNDLE_MANIFEST_VERSION_FOR_OSGI_R4); } /** * Returns the lowest bundle manifest version for which scoping is supported * * @return the lowest bundle manifest version for which scoping is supported */ public int getLowestSupportedVersion() { return BUNDLE_MANIFEST_VERSION_FOR_OSGI_R4; } } public static class DuplicateBundleSymbolicNameException extends Exception { private static final long serialVersionUID = -4228236795055040322L; private final String bundleSymbolicName; public DuplicateBundleSymbolicNameException(String bundleSymbolicName) { this.bundleSymbolicName = bundleSymbolicName; } public String getBundleSymbolicName() { return this.bundleSymbolicName; } } }