/******************************************************************************* * Copyright (c) 2010 SAP AG * 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: * Hristo Iliev, SAP AG - initial contribution *******************************************************************************/ package org.eclipse.virgo.shell.osgicommand.helper; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.osgi.service.resolver.ExportPackageDescription; import org.eclipse.osgi.service.resolver.PlatformAdmin; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; import org.osgi.service.packageadmin.PackageAdmin; import java.net.URL; import java.util.*; /** * Helper for class loading supporting commands */ @SuppressWarnings("deprecation") public class ClassLoadingHelper { /** * Determines if a class package is exported by a bundle * * @param bundleContext Bundle context for interaction with the OSGi framework * @param classPackage Class package to check for * @param testBundle The bundle that has to be tested * @return TRUE if the bundle is exported by the package, FALSE if the class is not exported or it does not have a package */ public static boolean isPackageExported(BundleContext bundleContext, String classPackage, Bundle testBundle) { ServiceReference<PlatformAdmin> reference = bundleContext.getServiceReference(PlatformAdmin.class); PlatformAdmin platformAdmin = bundleContext.getService(reference); BundleDescription bundleDescription = platformAdmin.getState(false).getBundle(testBundle.getBundleId()); ExportPackageDescription[] exportDescriptions = bundleDescription.getSelectedExports(); for (ExportPackageDescription exportDescription : exportDescriptions) { if (exportDescription.getName().equals(convertToClassName(classPackage))) return true; } // not found return false; } /** * Returns the bundles that can load a class and the originating bundle in a map * * @param bundleContext Bundle context for interaction with the OSGi framework * @param className Fully qualified class name (in the form <package>.<class name>) * @return Map between the bundles that can load the class and the bundle that provides it in each case */ public static Map<Bundle, Bundle> getBundlesLoadingClass(BundleContext bundleContext, String className) { Bundle[] bundles = bundleContext.getBundles(); HashMap<Bundle, Bundle> foundBundles = new HashMap<Bundle, Bundle>(); for (Bundle bundle : bundles) { Bundle originBundle = getOriginBundleOfClass(className, bundleContext, bundle); if (originBundle != null) { foundBundles.put(bundle, originBundle); } } return foundBundles; } /** * Find the originating bundle of a class loaded by a bundle * * @param className Fully qualified class name (in the form <package>.<class name> name) * @param bundleContext Bundle context for interaction with the OSGi framework * @param loadingBundle Bundle instance to load class from * @return originating {@link Bundle} or null if it cannot be loaded by <code>testBundle</code> */ public static Bundle getOriginBundleOfClass(String className, BundleContext bundleContext, Bundle loadingBundle) { Class<?> clasz = tryToLoadClass(className, loadingBundle); Bundle originBundle = null; if (clasz != null) { originBundle = FrameworkUtil.getBundle(clasz); if (originBundle == null) { // this is the system bundle originBundle = bundleContext.getBundle(0); } } return originBundle; } /** * Tries to load a class * * @param className Fully qualified class name (in the form <package>.<class name>) * @param bundle Bundle instance that has to be checked * @return The loaded class or null if it cannot be loaded from this bundle */ public static Class<?> tryToLoadClass(String className, Bundle bundle) { if (bundle == null) return null; try { return bundle.loadClass(convertToClassName(className)); } catch (ClassNotFoundException e) { // do nothing - if the class is not found we don't care } return null; } /** * Converts resource path (/javax/servlet/Servlet.class) to class name (javax.servlet.Servlet) * * @param resourcePath Path to the resource * @return Class name */ public static String convertToClassName(String resourcePath) { if (resourcePath == null) return null; resourcePath = resourcePath.replace("/", "."); if (resourcePath.startsWith(".")) { resourcePath = resourcePath.substring(1); } if (resourcePath.endsWith(".class")) { resourcePath = resourcePath.substring(0, resourcePath.length() - 6); } return resourcePath; } /** * Convert from package to path format (javax.servlet.Servlet --> javax/servlet/Servlet.class) * * @param className Class name * @return Path to a resource */ public static String convertToResourcePath(String className) { if (className == null) return null; String result = className; if (!className.contains("/") && !className.contains("*")) { if (className.endsWith(".class")) { result = className.substring(0, className.length() - 6); } return result.replace(".", "/") + ".class"; } return result; } /** * Returns all bundles that can load a class * * @param bundleContext Bundle context for interaction with the OSGi framework * @param className Fully qualified class name (in the form <package>.<class name>) * @param bundle Bundle name or ID that has to be checked * @return Map between the bundle that can load the class (key) and the one that provides it (value) * @throws IllegalArgumentException if there is no bundle with such name/id */ public static Map<Bundle, Bundle> getBundlesLoadingClass(BundleContext bundleContext, String className, String bundle) throws IllegalArgumentException { HashMap<Bundle, Bundle> result = new HashMap<Bundle, Bundle>(); long id = Long.MIN_VALUE; try { id = Long.parseLong(bundle); } catch (NumberFormatException e) { // not a number - then it is bundle name } if (id >= 0) { Bundle testBundle = bundleContext.getBundle(id); if (testBundle == null) throw new IllegalArgumentException("Bundle with ID [" + id + "] not found"); Bundle originBundle = getOriginBundleOfClass(className, bundleContext, testBundle); if (originBundle != null) { result.put(testBundle, originBundle); } } else { ServiceReference<PackageAdmin> reference = bundleContext.getServiceReference(PackageAdmin.class); PackageAdmin packageAdmin = bundleContext.getService(reference); Bundle[] bundles = packageAdmin.getBundles(bundle, null); if (bundles == null) throw new IllegalArgumentException("Bundle with symbolic name [" + bundle + "] not found"); for (Bundle testBundle : bundles) { Bundle originBundle = getOriginBundleOfClass(className, bundleContext, testBundle); if (originBundle != null) { result.put(testBundle, originBundle); } } } return result; } /** * Returns all bundles that contain a class * * @param bundleContext Bundle context for interaction with the OSGi framework * @param resourcePattern Search pattern in the form <package>/<class name>. The pattern can contain wildcards * @return Map between the bundle (key) and the URL(s) of the resources (value) */ public static Map<Bundle, List<String>> getBundlesContainingResource(BundleContext bundleContext, String resourcePattern) { Map<Bundle, List<String>> result = new HashMap<Bundle, List<String>>(); Bundle[] bundles = bundleContext.getBundles(); for (Bundle bundle : bundles) { List<String> entries = findEntries(bundle, resourcePattern); if (entries != null && entries.size() != 0) { result.put(bundle, entries); } } return result; } /** * Returns a list with bundle entries matching a resource pattern * * @param bundle Bundle to scan for entries * @param resourcePattern Pattern used for matching * @return List with found entries */ private static List<String> findEntries(Bundle bundle, String resourcePattern) { HashSet<String> urls = new HashSet<String>(); int index = resourcePattern.lastIndexOf("/"); if (index != -1) { String resourcePath = resourcePattern.substring(0, index); String resourceEntity = resourcePattern.substring(index + 1); // Search the whole bundle for entity starting from the root. We need this since "the pattern is only // matched against the last element of the entry path" as stated in findEntries JavaDoc. This means that // web bundle that packages a class in WEB-INF/classes will not be found by findEntries since the path is // prepended with WEB-INF/classes. Therefore we search for a class everywhere in the bundle and then // filter the result. addURLs(urls, bundle.findEntries("/", resourceEntity, true), resourcePath); } // Search the root of the bundle for entity matching the specified pattern addURLs(urls, bundle.findEntries("/", resourcePattern, true), null); return new ArrayList<String>(urls); } /** * Adds all found resources eliminating the duplicates or the ones that do not contain the requested path * * @param urls Result set with URLs as string * @param foundURLs Enumeration to scan * @param path Expected path of the entities. The entities are not put in the result set unless they contain * this path. */ private static void addURLs(HashSet<String> urls, Enumeration<URL> foundURLs, String path) { if (foundURLs != null) { while (foundURLs.hasMoreElements()) { String url = foundURLs.nextElement().getFile(); if (path != null && !url.contains(path)) { continue; } urls.add(url); } } } }