/******************************************************************************* * Copyright (c) 2005, 2016 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.osgi.internal.loader; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.security.*; import java.security.cert.Certificate; import java.util.*; import org.eclipse.osgi.container.ModuleRevision; import org.eclipse.osgi.internal.debug.Debug; import org.eclipse.osgi.internal.framework.EquinoxConfiguration; import org.eclipse.osgi.internal.loader.classpath.ClasspathEntry; import org.eclipse.osgi.internal.loader.classpath.ClasspathManager; import org.eclipse.osgi.signedcontent.SignedContent; import org.eclipse.osgi.signedcontent.SignerInfo; import org.eclipse.osgi.storage.BundleInfo.Generation; import org.eclipse.osgi.storage.bundlefile.BundleFile; import org.eclipse.osgi.storage.bundlefile.BundleFileWrapperChain; import org.osgi.framework.Bundle; import org.osgi.framework.BundleReference; public abstract class ModuleClassLoader extends ClassLoader implements BundleReference { public static class GenerationProtectionDomain extends ProtectionDomain implements BundleReference { private final Generation generation; public GenerationProtectionDomain(CodeSource codesource, PermissionCollection permissions, Generation generation) { super(codesource, permissions); this.generation = generation; } public Bundle getBundle() { return generation.getRevision().getBundle(); } } /** * A PermissionCollection for AllPermissions; shared across all ProtectionDomains when security is disabled */ protected static final PermissionCollection ALLPERMISSIONS; protected static final boolean REGISTERED_AS_PARALLEL = ClassLoader.registerAsParallelCapable(); static { AllPermission allPerm = new AllPermission(); ALLPERMISSIONS = allPerm.newPermissionCollection(); if (ALLPERMISSIONS != null) ALLPERMISSIONS.add(allPerm); } /** * Holds the result of a defining a class. * */ public static class DefineClassResult { /** * The class object that either got defined or was found as previously loaded. */ public final Class<?> clazz; /** * Set to true if the class object got defined; set to false if the class * did not get defined correctly or the class was found as a previously loaded * class. */ public final boolean defined; public DefineClassResult(Class<?> clazz, boolean defined) { this.clazz = clazz; this.defined = defined; } } private final Map<String, Thread> classNameLocks = new HashMap<>(5); private final Object pkgLock = new Object(); /** * Constructs a new ModuleClassLoader. * @param parent the parent classloader */ public ModuleClassLoader(ClassLoader parent) { super(parent); } /** * Returns the generation of the host revision associated with this class loader * @return the generation for this class loader */ protected abstract Generation getGeneration(); /** * Returns the Debug object for the Framework instance * @return the Debug object for the Framework instance */ protected abstract Debug getDebug(); /** * Returns the classpath manager for this class loader * @return the classpath manager for this class loader */ public abstract ClasspathManager getClasspathManager(); /** * Returns the configuration for the Framework instance * @return the configuration for the Framework instance */ protected abstract EquinoxConfiguration getConfiguration(); /** * Returns the bundle loader for this class loader * @return the bundle loader for this class loader */ public abstract BundleLoader getBundleLoader(); /** * Returns true if this class loader implementation has been * registered with the JVM as a parallel class loader. * This requires Java 7 or later. * @return true if this class loader implementation has been * registered with the JVM as a parallel class loader; otherwise * false is returned. */ public abstract boolean isRegisteredAsParallel(); /** * Loads a class for the bundle. First delegate.findClass(name) is called. * The delegate will query the system class loader, bundle imports, bundle * local classes, bundle hosts and fragments. The delegate will call * BundleClassLoader.findLocalClass(name) to find a class local to this * bundle. * @param name the name of the class to load. * @param resolve indicates whether to resolve the loaded class or not. * @return The Class object. * @throws ClassNotFoundException if the class is not found. */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (getDebug().DEBUG_LOADER) Debug.println("ModuleClassLoader[" + getBundleLoader() + "].loadClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ try { // Just ask the delegate. This could result in findLocalClass(name) being called. Class<?> clazz = getBundleLoader().findClass(name); // resolve the class if asked to. if (resolve) resolveClass(clazz); return (clazz); } catch (Error e) { if (getDebug().DEBUG_LOADER) { Debug.println("ModuleClassLoader[" + getBundleLoader() + "].loadClass(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ Debug.printStackTrace(e); } throw e; } catch (ClassNotFoundException e) { // If the class is not found do not try to look for it locally. // The delegate would have already done that for us. if (getDebug().DEBUG_LOADER) { Debug.println("ModuleClassLoader[" + getBundleLoader() + "].loadClass(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ Debug.printStackTrace(e); } throw e; } } // preparing for Java 9 protected Class<?> findClass(String moduleName, String name) { try { return findLocalClass(name); } catch (ClassNotFoundException e) { return null; } } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return findLocalClass(name); } /** * Gets a resource for the bundle. First delegate.findResource(name) is * called. The delegate will query the system class loader, bundle imports, * bundle local resources, bundle hosts and fragments. The delegate will * call BundleClassLoader.findLocalResource(name) to find a resource local * to this bundle. * @param name The resource path to get. * @return The URL of the resource or null if it does not exist. */ public URL getResource(String name) { if (getDebug().DEBUG_LOADER) { Debug.println("ModuleClassLoader[" + getBundleLoader() + "].getResource(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } URL url = getBundleLoader().findResource(name); if (url != null) return (url); if (getDebug().DEBUG_LOADER) { Debug.println("ModuleClassLoader[" + getBundleLoader() + "].getResource(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } return (null); } // preparing for Java 9 protected URL findResource(String moduleName, String name) { return findLocalResource(name); } @Override protected URL findResource(String name) { return findLocalResource(name); } /** * Gets resources for the bundle. First delegate.findResources(name) is * called. The delegate will query the system class loader, bundle imports, * bundle local resources, bundle hosts and fragments. The delegate will * call BundleClassLoader.findLocalResources(name) to find a resource local * to this bundle. * @param name The resource path to get. * @return The Enumeration of the resource URLs. */ public Enumeration<URL> getResources(String name) throws IOException { if (getDebug().DEBUG_LOADER) { Debug.println("ModuleClassLoader[" + getBundleLoader() + "].getResources(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } Enumeration<URL> result = getBundleLoader().findResources(name); if (getDebug().DEBUG_LOADER) { if (result == null || !result.hasMoreElements()) { Debug.println("ModuleClassLoader[" + getBundleLoader() + "].getResources(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } return result; } @Override protected Enumeration<URL> findResources(String name) throws IOException { return findLocalResources(name); } /** * Finds a library for this bundle. Simply calls * manager.findLibrary(libname) to find the library. * @param libname The library to find. * @return The absolution path to the library or null if not found */ protected String findLibrary(String libname) { // let the manager find the library for us return getClasspathManager().findLibrary(libname); } public ClasspathEntry createClassPathEntry(BundleFile bundlefile, Generation entryGeneration) { return new ClasspathEntry(bundlefile, createProtectionDomain(bundlefile, entryGeneration), entryGeneration); } public DefineClassResult defineClass(String name, byte[] classbytes, ClasspathEntry classpathEntry) { // Note that we must check findLoadedClass again here since no locks are held between // calling findLoadedClass the first time and defineClass. // This is to allow weavers to get called while holding no locks. // See ClasspathManager.findLocalClass(String) boolean defined = false; Class<?> result = null; if (isRegisteredAsParallel()) { // lock by class name in this case boolean initialLock = lockClassName(name); try { result = findLoadedClass(name); if (result == null) { result = defineClass(name, classbytes, 0, classbytes.length, classpathEntry.getDomain()); defined = true; } } finally { if (initialLock) { unlockClassName(name); } } } else { // lock by class loader instance in this case synchronized (this) { result = findLoadedClass(name); if (result == null) { result = defineClass(name, classbytes, 0, classbytes.length, classpathEntry.getDomain()); defined = true; } } } return new DefineClassResult(result, defined); } public Class<?> publicFindLoaded(String classname) { if (isRegisteredAsParallel()) { return findLoadedClass(classname); } synchronized (this) { return findLoadedClass(classname); } } public Package publicGetPackage(String pkgname) { synchronized (pkgLock) { return getPackage(pkgname); } } public Package publicDefinePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) { synchronized (pkgLock) { Package pkg = getPackage(name); return pkg != null ? pkg : definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); } } public URL findLocalResource(String resource) { return getClasspathManager().findLocalResource(resource); } public Enumeration<URL> findLocalResources(String resource) { return getClasspathManager().findLocalResources(resource); } public Class<?> findLocalClass(String classname) throws ClassNotFoundException { return getClasspathManager().findLocalClass(classname); } /** * Creates a ProtectionDomain which uses specified BundleFile and the permissions of the baseDomain * @param bundlefile The source bundlefile the domain is for. * @param domainGeneration the source generation for the domain * @return a ProtectionDomain which uses specified BundleFile and the permissions of the baseDomain */ @SuppressWarnings("deprecation") protected ProtectionDomain createProtectionDomain(BundleFile bundlefile, Generation domainGeneration) { // create a protection domain which knows about the codesource for this classpath entry (bug 89904) ProtectionDomain baseDomain = domainGeneration.getDomain(); try { // use the permissions supplied by the domain passed in from the framework PermissionCollection permissions; if (baseDomain != null) { permissions = baseDomain.getPermissions(); } else { // no domain specified. Better use a collection that has all permissions // this is done just incase someone sets the security manager later permissions = ALLPERMISSIONS; } Certificate[] certs = null; SignedContent signedContent = null; if (bundlefile instanceof BundleFileWrapperChain) { BundleFileWrapperChain wrapper = (BundleFileWrapperChain) bundlefile; while (wrapper != null && (!(wrapper.getWrapped() instanceof SignedContent))) wrapper = wrapper.getNext(); signedContent = wrapper == null ? null : (SignedContent) wrapper.getWrapped(); } if (getConfiguration().CLASS_CERTIFICATE && signedContent != null && signedContent.isSigned()) { SignerInfo[] signers = signedContent.getSignerInfos(); if (signers.length > 0) certs = signers[0].getCertificateChain(); } File file = bundlefile.getBaseFile(); // Bug 477787: file will be null when the osgi.framework configuration property contains an invalid value. return new GenerationProtectionDomain(file == null ? null : new CodeSource(file.toURL(), certs), permissions, getGeneration()); //return new ProtectionDomain(new CodeSource(bundlefile.getBaseFile().toURL(), certs), permissions); } catch (MalformedURLException e) { // Failed to create our own domain; just return the baseDomain return baseDomain; } } public Bundle getBundle() { return getGeneration().getRevision().getBundle(); } public List<URL> findEntries(String path, String filePattern, int options) { return getClasspathManager().findEntries(path, filePattern, options); } public Collection<String> listResources(String path, String filePattern, int options) { return getBundleLoader().listResources(path, filePattern, options); } public Collection<String> listLocalResources(String path, String filePattern, int options) { return getClasspathManager().listLocalResources(path, filePattern, options); } public String toString() { Bundle b = getBundle(); StringBuffer result = new StringBuffer(super.toString()); if (b == null) return result.toString(); return result.append('[').append(b.getSymbolicName()).append(':').append(b.getVersion()).append("(id=").append(b.getBundleId()).append(")]").toString(); //$NON-NLS-1$//$NON-NLS-2$ } public void loadFragments(Collection<ModuleRevision> fragments) { getClasspathManager().loadFragments(fragments); } private boolean lockClassName(String classname) { synchronized (classNameLocks) { Object lockingThread = classNameLocks.get(classname); Thread current = Thread.currentThread(); if (lockingThread == current) return false; boolean previousInterruption = Thread.interrupted(); try { while (true) { if (lockingThread == null) { classNameLocks.put(classname, current); return true; } classNameLocks.wait(); lockingThread = classNameLocks.get(classname); } } catch (InterruptedException e) { previousInterruption = true; // must not throw LinkageError or ClassNotFoundException here because that will cause all threads // to fail to load the class (see bug 490902) throw new Error("Interrupted while waiting for classname lock: " + classname, e); //$NON-NLS-1$ } finally { if (previousInterruption) { current.interrupt(); } } } } private void unlockClassName(String classname) { synchronized (classNameLocks) { classNameLocks.remove(classname); classNameLocks.notifyAll(); } } public void close() { getClasspathManager().close(); } }