/* * 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.tuscany.sca.tools.maven.compiler.osgi; import static org.eclipse.osgi.service.resolver.ResolverError.IMPORT_PACKAGE_USES_CONFLICT; import static org.eclipse.osgi.service.resolver.ResolverError.MISSING_FRAGMENT_HOST; import static org.eclipse.osgi.service.resolver.ResolverError.MISSING_IMPORT_PACKAGE; import static org.eclipse.osgi.service.resolver.ResolverError.MISSING_REQUIRE_BUNDLE; import static org.eclipse.osgi.service.resolver.ResolverError.REQUIRE_BUNDLE_USES_CONFLICT; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Manifest; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.logging.Logger; 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.StateObjectFactory; import org.eclipse.osgi.service.resolver.VersionConstraint; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; public class BundleResolver { private static final String PROP_MAVEN_PROJECT = "MavenProject"; private static final String PROP_MANIFEST = "BundleManifest"; private StateObjectFactory factory = StateObjectFactory.defaultFactory; private State state; private long id = 0; private Logger logger; public static BundleDescription[] getDependentBundles(BundleDescription root) { if (root == null) return new BundleDescription[0]; BundleDescription[] imported = getImportedBundles(root); BundleDescription[] required = getRequiredBundles(root); BundleDescription[] dependents = new BundleDescription[imported.length + required.length]; System.arraycopy(imported, 0, dependents, 0, imported.length); System.arraycopy(required, 0, dependents, imported.length, required.length); return dependents; } public static BundleDescription[] getImportedBundles(BundleDescription root) { if (root == null) return new BundleDescription[0]; ExportPackageDescription[] packages = root.getResolvedImports(); List resolvedImports = new ArrayList(packages.length); for (int i = 0; i < packages.length; i++) if (!root.getLocation().equals(packages[i].getExporter().getLocation()) && !resolvedImports .contains(packages[i].getExporter())) resolvedImports.add(packages[i].getExporter()); return (BundleDescription[])resolvedImports.toArray(new BundleDescription[resolvedImports.size()]); } public static BundleDescription[] getRequiredBundles(BundleDescription root) { if (root == null) return new BundleDescription[0]; return root.getResolvedRequires(); } public BundleResolver(Logger logger) { this.logger = logger; this.state = factory.createState(true); Properties props = new Properties(); props.putAll(System.getProperties()); BundleUtil.loadVMProfile(props); state.setPlatformProperties(props); URL url = Bundle.class.getProtectionDomain().getCodeSource().getLocation(); File osgi = toFile(url); try { addBundle(osgi); } catch (BundleException e) { throw new IllegalArgumentException(e); } } private long getNextId() { return ++id; } public BundleDescription addBundle(File bundleLocation) throws BundleException { return addBundle(bundleLocation, false); } public BundleDescription addBundle(File bundleLocation, boolean override) throws BundleException { if (bundleLocation == null || !bundleLocation.exists()) throw new IllegalArgumentException("bundleLocation not found: " + bundleLocation); Dictionary manifest = loadManifestAttributes(bundleLocation); if (manifest == null) { // throw new BundleException("manifest not found in " + bundleLocation); return null; } return addBundle(manifest, bundleLocation, override); } public BundleDescription addBundle(File manifestLocation, File bundleLocation, boolean override) throws BundleException { if (bundleLocation == null || !bundleLocation.exists()) throw new IllegalArgumentException("bundleLocation not found: " + bundleLocation); Dictionary manifest = loadManifestAttributes(manifestLocation); if (manifest == null) throw new IllegalArgumentException("manifest not found in " + manifestLocation); return addBundle(manifest, bundleLocation, override); } private Dictionary loadManifestAttributes(File bundleLocation) { Manifest m = loadManifest(bundleLocation); if (m == null) { return null; } return manifestToProperties(m.getMainAttributes()); } public Manifest loadManifest(File bundleLocation) { try { return BundleUtil.getManifest(bundleLocation); } catch (IOException e) { e.printStackTrace(); return null; } } private Properties manifestToProperties(Attributes d) { Iterator iter = d.keySet().iterator(); Properties result = new Properties(); while (iter.hasNext()) { Attributes.Name key = (Attributes.Name)iter.next(); result.put(key.toString(), d.get(key)); } return result; } private BundleDescription addBundle(Dictionary enhancedManifest, File bundleLocation, boolean override) throws BundleException { BundleDescription descriptor = factory.createBundleDescription(state, enhancedManifest, bundleLocation.getAbsolutePath(), getNextId()); setUserProperty(descriptor, PROP_MANIFEST, enhancedManifest); if (override) { BundleDescription[] conflicts = state.getBundles(descriptor.getSymbolicName()); if (conflicts != null) { for (BundleDescription conflict : conflicts) { state.removeBundle(conflict); logger .warn(conflict.toString() + " has been replaced by another bundle with the same symbolic name " + descriptor.toString()); } } } state.addBundle(descriptor); return descriptor; } public BundleDescription getResolvedBundle(String bundleId) { BundleDescription[] description = state.getBundles(bundleId); if (description == null) return null; for (int i = 0; i < description.length; i++) { if (description[i].isResolved()) return description[i]; } return null; } public void resolveState() { state.resolve(false); if (logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder("Resolved OSGi state\n"); for (BundleDescription bundle : state.getBundles()) { if (!bundle.isResolved()) { sb.append("[X] "); } else { sb.append("[V] "); } sb.append(bundle).append(": (").append(bundle.getLocation()); sb.append(")\n"); for (ResolverError error : state.getResolverErrors(bundle)) { sb.append(" ").append(error.toString()).append('\n'); } } logger.debug(sb.toString()); } } public State getState() { return state; } public BundleDescription[] getBundles() { return state.getBundles(); } public ResolverError[] getResolverErrors(BundleDescription bundle) { Set errors = new LinkedHashSet(); getRelevantErrors(errors, bundle); return (ResolverError[])errors.toArray(new ResolverError[errors.size()]); } private void getRelevantErrors(Set errors, BundleDescription bundle) { ResolverError[] bundleErrors = state.getResolverErrors(bundle); for (int j = 0; j < bundleErrors.length; j++) { ResolverError error = bundleErrors[j]; errors.add(error); VersionConstraint constraint = error.getUnsatisfiedConstraint(); if (constraint instanceof BundleSpecification || constraint instanceof HostSpecification) { BundleDescription[] requiredBundles = state.getBundles(constraint.getName()); for (int i = 0; i < requiredBundles.length; i++) { getRelevantErrors(errors, requiredBundles[i]); } } } } private void logError(BundleDescription bundle, int level, Object object) { StringBuffer msg = new StringBuffer(); for (int i = 0; i < level; i++) { msg.append("--"); } msg.append("> [").append(bundle.getSymbolicName()).append("] "); msg.append(object); logger.error(msg.toString()); } public void analyzeErrors(BundleDescription bundle) { analyzeErrors(bundle, new HashSet(), 1); } private void analyzeErrors(BundleDescription bundle, Set bundles, int level) { if (bundles.contains(bundle)) { return; } bundles.add(bundle); ResolverError[] errors = state.getResolverErrors(bundle); for (ResolverError error : errors) { logError(bundle, level, error); VersionConstraint constraint = error.getUnsatisfiedConstraint(); switch (error.getType()) { case MISSING_IMPORT_PACKAGE: ImportPackageSpecification pkgSpec = (ImportPackageSpecification)constraint; for (BundleDescription b : getBundles()) { for (ExportPackageDescription pkg : b.getExportPackages()) { if (pkg.getName().equals(pkgSpec.getName())) { if (pkgSpec.getVersionRange().isIncluded(pkg.getVersion())) { if (!pkg.getExporter().isResolved()) { logError(b, level, "Bundle unresolved: " + pkg); analyzeErrors(pkg.getExporter(), bundles, level + 1); } } else { logError(b, level, "Version mismatch: " + pkgSpec + " " + pkg); } } } } break; case MISSING_REQUIRE_BUNDLE: case MISSING_FRAGMENT_HOST: // BundleSpecification bundleSpec = (BundleSpecification)constraint; for (BundleDescription b : getBundles()) { if (b == bundle) { continue; } if (b.getSymbolicName().equals(constraint.getName())) { if (constraint.getVersionRange().isIncluded(b.getVersion())) { // There must be something wrong in the bundle analyzeErrors(b, bundles, level); } else { logError(bundle, level, "Version mismatch: " + constraint + " " + b); } } } break; case IMPORT_PACKAGE_USES_CONFLICT: case REQUIRE_BUNDLE_USES_CONFLICT: default: logger.error(reportErrors(bundle)); break; } } } public Set getAllErrors() { BundleDescription[] bundles = state.getBundles(); Set errors = new LinkedHashSet(); for (int i = 0; i < bundles.length; i++) { BundleDescription bundle = bundles[i]; ResolverError[] bundleErrors = state.getResolverErrors(bundle); if (bundleErrors != null) { errors.addAll(Arrays.asList(bundleErrors)); } } return errors; } public List getDependencies(BundleDescription desc) { Set bundleIds = new LinkedHashSet(); addBundleAndDependencies(desc, bundleIds, true); List dependencies = new ArrayList(); for (long bundleId : bundleIds) { if (desc.getBundleId() != bundleId) { BundleDescription dependency = state.getBundle(bundleId); BundleDescription supplier = dependency.getSupplier().getSupplier(); HostSpecification host = supplier.getHost(); if (host == null || !desc.equals(host.getSupplier())) { dependencies.add(dependency); } } } return dependencies; } /** * Code below is copy&paste from org.eclipse.pde.internal.core.DependencyManager * which seems to calculate runtime dependencies. In particular, it adds * fragments' dependencies to the host bundle (see TychoTest#testFragment unit test). * This may or may not cause problems... * * RequiredPluginsClasspathContainer#computePluginEntries has the logic to * calculate compile-time dependencies in IDE. * * TODO find the code used by PDE/Build */ private static void addBundleAndDependencies(BundleDescription desc, Set bundleIds, boolean includeOptional) { if (desc != null && bundleIds.add(new Long(desc.getBundleId()))) { BundleSpecification[] required = desc.getRequiredBundles(); for (int i = 0; i < required.length; i++) { if (includeOptional || !required[i].isOptional()) addBundleAndDependencies((BundleDescription)required[i].getSupplier(), bundleIds, includeOptional); } ImportPackageSpecification[] importedPkgs = desc.getImportPackages(); for (int i = 0; i < importedPkgs.length; i++) { ExportPackageDescription exporter = (ExportPackageDescription)importedPkgs[i].getSupplier(); // Continue if the Imported Package is unresolved of the package is optional and don't want optional packages if (exporter == null || (!includeOptional && Constants.RESOLUTION_OPTIONAL.equals(importedPkgs[i] .getDirective(Constants.RESOLUTION_DIRECTIVE)))) continue; addBundleAndDependencies(exporter.getExporter(), bundleIds, includeOptional); } BundleDescription[] fragments = desc.getFragments(); for (int i = 0; i < fragments.length; i++) { if (!fragments[i].isResolved()) continue; String id = fragments[i].getSymbolicName(); if (!"org.eclipse.ui.workbench.compatibility".equals(id)) //$NON-NLS-1$ addBundleAndDependencies(fragments[i], bundleIds, includeOptional); } HostSpecification host = desc.getHost(); if (host != null) addBundleAndDependencies((BundleDescription)host.getSupplier(), bundleIds, includeOptional); } } public BundleDescription getBundleDescription(MavenProject project) { String location = project.getFile().getParentFile().getAbsolutePath(); return state.getBundleByLocation(location); } public BundleDescription getBundleDescription(File location) { String absolutePath = location.getAbsolutePath(); return state.getBundleByLocation(absolutePath); } private static void setUserProperty(BundleDescription desc, String name, Object value) { Object userObject = desc.getUserObject(); if (userObject != null && !(userObject instanceof Map)) { throw new IllegalStateException("Unexpected user object " + desc.toString()); } Map props = (Map)userObject; if (props == null) { props = new HashMap(); desc.setUserObject(props); } props.put(name, value); } private static Object getUserProperty(BundleDescription desc, String name) { Object userObject = desc.getUserObject(); if (userObject instanceof Map) { return ((Map)userObject).get(name); } return null; } public MavenProject getMavenProject(BundleDescription desc) { return (MavenProject)getUserProperty(desc, PROP_MAVEN_PROJECT); } public void assertResolved(BundleDescription desc) throws BundleException { if (!desc.isResolved()) { throw new BundleException("Bundle cannot be resolved: " + desc); } } public String reportErrors(BundleDescription desc) { StringBuffer msg = new StringBuffer(); msg.append("Bundle ").append(desc.getSymbolicName()).append(" cannot be resolved: \n"); BundleDescription[] bundles = state.getBundles(); int index = 0; for (BundleDescription b : bundles) { if (b.isResolved()) { continue; } ResolverError[] errors = state.getResolverErrors(b); if (errors.length > 0) { msg.append(" ").append("[").append(index++).append("] ").append(b.getSymbolicName()).append("\n"); } for (int i = 0; i < errors.length; i++) { ResolverError error = errors[i]; msg.append(" -->").append(error).append("\n"); } } return msg.toString(); } public String getManifestAttribute(BundleDescription desc, String attr) { Dictionary mf = (Dictionary)getUserProperty(desc, PROP_MANIFEST); if (mf != null) { return (String)mf.get(attr); } return null; } private static File toFile(URL url) { if (url.getProtocol().equals("file") == false) { return null; } else { String filename = url.getFile().replace('/', File.separatorChar).replace("%20", " "); return new File(filename); } } /* public static void main(String[] args) throws Exception { BundleResolver resolver = new BundleResolver(new ConsoleLogger(Logger.LEVEL_INFO, "tuscany")); String home = System.getProperty("user.home"); File jar = new File(new File(home), ".m2/repository/org/apache/tuscany/sca/tuscany-sca-api/1.4-EQUINOX-SNAPSHOT/tuscany-sca-api-1.4-EQUINOX-SNAPSHOT.jar"); BundleDescription bundle = resolver.addBundle(jar); resolver.resolveState(); resolver.assertResolved(bundle); } */ }