| /******************************************************************************* |
| * Copyright (c) 2003,2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.runtime.adaptor; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.security.ProtectionDomain; |
| import java.util.*; |
| import java.util.jar.Attributes; |
| import java.util.jar.Manifest; |
| import org.eclipse.osgi.framework.adaptor.BundleData; |
| import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; |
| import org.eclipse.osgi.framework.adaptor.core.*; |
| import org.eclipse.osgi.framework.internal.core.AbstractBundle; |
| import org.eclipse.osgi.framework.internal.core.Msg; |
| import org.eclipse.osgi.framework.internal.defaultadaptor.DefaultClassLoader; |
| import org.eclipse.osgi.framework.internal.defaultadaptor.DevClassPathHelper; |
| import org.eclipse.osgi.framework.log.FrameworkLogEntry; |
| import org.eclipse.osgi.framework.stats.ClassloaderStats; |
| import org.eclipse.osgi.framework.stats.ResourceBundleStats; |
| import org.eclipse.osgi.util.ManifestElement; |
| import org.osgi.framework.*; |
| |
| public class EclipseClassLoader extends DefaultClassLoader { |
| private static String[] NL_JAR_VARIANTS = buildNLJarVariants(System.getProperties().getProperty("osgi.nl")); //$NON-NLS-1$ |
| // from Eclipse-AutoStart element value |
| private boolean autoStart; |
| // from Eclipse-AutoStart's "exceptions" attribute |
| private String[] exceptions; |
| |
| public EclipseClassLoader(ClassLoaderDelegate delegate, ProtectionDomain domain, String[] classpath, ClassLoader parent, BundleData bundleData) { |
| super(delegate, domain, classpath, parent, (org.eclipse.osgi.framework.internal.defaultadaptor.DefaultBundleData) bundleData); |
| parseAutoStart(bundleData); |
| } |
| private void parseAutoStart(BundleData bundleData) { |
| try { |
| String automationHeader = (String) bundleData.getManifest().get(EclipseAdaptorConstants.ECLIPSE_AUTOSTART); |
| ManifestElement[] allElements = ManifestElement.parseHeader(EclipseAdaptorConstants.ECLIPSE_AUTOSTART, automationHeader); |
| //Eclipse-AutoStart not found... look for the Legacy header instead //TODO This is old code, this can be removed |
| if (allElements == null) { |
| autoStart = "true".equalsIgnoreCase((String) bundleData.getManifest().get(EclipseAdaptorConstants.LEGACY)); //$NON-NLS-1$ |
| return; |
| } |
| // the single value for this element should be true|false |
| autoStart = "true".equalsIgnoreCase(allElements[0].getValue()); //$NON-NLS-1$ |
| // look for any exceptions (the attribute) to the autoActivate setting |
| String exceptionsValue = allElements[0].getAttribute(EclipseAdaptorConstants.EXCEPTIONS_ATTRIBUTE); |
| if (exceptionsValue != null) { |
| StringTokenizer tokenizer = new StringTokenizer(exceptionsValue, ","); //$NON-NLS-1$ |
| int numberOfTokens = tokenizer.countTokens(); |
| exceptions = new String[numberOfTokens]; |
| for (int i = 0; i < numberOfTokens; i++) { |
| exceptions[i] = tokenizer.nextToken().trim(); |
| } |
| } |
| } catch (BundleException e) { |
| // just use the default settings (no auto activation) |
| String message = EclipseAdaptorMsg.formatter.getString("ECLIPSE_CLASSLOADER_CANNOT_GET_HEADERS", bundleData.getLocation()); //$NON-NLS-1$ |
| EclipseAdaptor.getDefault().getFrameworkLog().log(new FrameworkLogEntry(EclipseAdaptorConstants.PI_ECLIPSE_OSGI, message, 0, e, null)); |
| } |
| } |
| |
| public Class findLocalClass(String name) throws ClassNotFoundException { |
| if (EclipseAdaptor.MONITOR_CLASSES) //Suport for performance analysis |
| ClassloaderStats.startLoadingClass(getClassloaderId(), name); |
| boolean found = true; |
| |
| try { |
| AbstractBundle bundle = (AbstractBundle) hostdata.getBundle(); |
| // If the bundle is active, just return the class |
| if (bundle.getState() == AbstractBundle.ACTIVE) |
| return super.findLocalClass(name); |
| |
| //If the bundle is uninstalled, classes can still be loaded from it |
| if (bundle.getState() == AbstractBundle.UNINSTALLED) |
| return super.findLocalClass(name); |
| |
| //If the bundle is stopping, we don't want to reactive it but we still want to be able to load classes from it. |
| if (bundle.getState() == AbstractBundle.STOPPING) |
| return super.findLocalClass(name); |
| |
| // The bundle is not active and does not require activation, just return the class |
| if (! shouldActivateFor(name)) |
| return super.findLocalClass(name); |
| |
| // The bundle is starting |
| if (bundle.getState() == AbstractBundle.STARTING) { |
| //If the thread trying to load the class is the one trying to activate the bundle, then return the class |
| if (bundle.testStateChanging(Thread.currentThread()) || bundle.testStateChanging(null)) |
| return super.findLocalClass(name); |
| |
| //If it's another thread, we wait and try again. In any case the class is returned. The difference is that an exception can be logged. |
| if (! bundle.testStateChanging(Thread.currentThread())) { |
| Object lock = bundle.getStateChangeLock(); |
| long start = System.currentTimeMillis(); |
| long delay = 5000; |
| long timeLeft = delay; |
| while (true) { |
| if (bundle.testStateChanging(null)) |
| break; |
| |
| if (timeLeft <= 0) |
| break; |
| try { |
| synchronized(lock) { |
| lock.wait(timeLeft); |
| } |
| } catch(InterruptedException e) { |
| //Ignore and keep waiting |
| } |
| timeLeft = start + delay - System.currentTimeMillis(); |
| } |
| if (timeLeft <= 0 || bundle.getState() != AbstractBundle.ACTIVE) { |
| String message = EclipseAdaptorMsg.formatter.getString("ECLIPSE_CLASSLOADER_CONCURRENT_STARTUP", new Object[] {Thread.currentThread(), name, bundle.getStateChanging().getName(), bundle.getSymbolicName()==null ? Long.toString(bundle.getBundleId()) : bundle.getSymbolicName()}); //$NON-NLS-1$ |
| EclipseAdaptor.getDefault().getFrameworkLog().log(new FrameworkLogEntry(EclipseAdaptorConstants.PI_ECLIPSE_OSGI, message, 0, null, null)); |
| } |
| return super.findLocalClass(name); |
| } |
| } |
| |
| //The bundle must be started. |
| try { |
| hostdata.getBundle().start(); |
| } catch (BundleException e) { |
| String message = EclipseAdaptorMsg.formatter.getString("ECLIPSE_CLASSLOADER_ACTIVATION", bundle.getSymbolicName(), Long.toString(bundle.getBundleId())); //$NON-NLS-1$ |
| EclipseAdaptor.getDefault().getFrameworkLog().log(new FrameworkLogEntry(EclipseAdaptorConstants.PI_ECLIPSE_OSGI, message, 0, e, null)); |
| } finally { |
| return super.findLocalClass(name); |
| } |
| } catch (ClassNotFoundException e) { |
| found = false; |
| throw e; |
| } finally { |
| if (EclipseAdaptor.MONITOR_CLASSES) |
| ClassloaderStats.endLoadingClass(getClassloaderId(), name, found); |
| } |
| } |
| /** |
| * Determines if for loading the given class we should activate the bundle. |
| */ |
| private boolean shouldActivateFor(String className) { |
| //Don't reactivate on shut down |
| if (EclipseAdaptor.stopping) |
| return false; |
| // no exceptions, it is easy to figure it out |
| if (exceptions == null) |
| return autoStart; |
| // otherwise, we need to check if the package is in the exceptions list |
| int dotPosition = className.lastIndexOf('.'); |
| // the class has no package name... no exceptions apply |
| if (dotPosition == -1) |
| return autoStart; |
| String packageName = className.substring(0, dotPosition); |
| // should activate if autoStart and package not in exceptions, or if !autoStart and package in exceptions |
| return autoStart ^ exceptionsContained(packageName); |
| } |
| private boolean exceptionsContained(String packageName) { |
| for (int i = 0; i < exceptions.length; i++) { |
| if(exceptions[i].equals(packageName)) |
| return true; |
| } |
| return false; |
| } |
| /** |
| * Override defineClass to allow for package defining. |
| */ |
| protected Class defineClass(String name, byte[] classbytes, int off, int len, ClasspathEntry classpathEntry) throws ClassFormatError { |
| // Define the package if it is not the default package. |
| int lastIndex = name.lastIndexOf('.'); |
| if (lastIndex != -1) { |
| String packageName = name.substring(0, lastIndex); |
| Package pkg = getPackage(packageName); |
| if (pkg == null) { |
| // get info about the package from the classpath entry's manifest. |
| String specTitle = null, specVersion = null, specVendor = null, implTitle = null, implVersion = null, implVendor = null; |
| Manifest mf = ((EclipseClasspathEntry) classpathEntry).getManifest(); |
| if (mf != null) { |
| Attributes mainAttributes = mf.getMainAttributes(); |
| String dirName = packageName.replace('.', '/') + '/'; |
| Attributes packageAttributes = mf.getAttributes(dirName); |
| boolean noEntry = false; |
| if (packageAttributes == null) { |
| noEntry = true; |
| packageAttributes = mainAttributes; |
| } |
| specTitle = packageAttributes.getValue(Attributes.Name.SPECIFICATION_TITLE); |
| if (specTitle == null && !noEntry) |
| specTitle = mainAttributes.getValue(Attributes.Name.SPECIFICATION_TITLE); |
| specVersion = packageAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION); |
| if (specVersion == null && !noEntry) |
| specVersion = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION); |
| specVendor = packageAttributes.getValue(Attributes.Name.SPECIFICATION_VENDOR); |
| if (specVendor == null && !noEntry) |
| specVendor = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VENDOR); |
| implTitle = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE); |
| if (implTitle == null && !noEntry) |
| implTitle = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE); |
| implVersion = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); |
| if (implVersion == null && !noEntry) |
| implVersion = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); |
| implVendor = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); |
| if (implVendor == null && !noEntry) |
| implVendor = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); |
| } |
| // The package is not defined yet define it before we define the class. |
| // TODO still need to seal packages. |
| definePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, null); |
| } |
| } |
| return super.defineClass(name, classbytes, off, len, classpathEntry); |
| } |
| private String getClassloaderId() { |
| return hostdata.getBundle().getSymbolicName(); |
| } |
| public URL getResouce(String name) { |
| URL result = super.getResource(name); |
| if (EclipseAdaptor.MONITOR_RESOURCE_BUNDLES) { |
| if (result != null && name.endsWith(".properties")) { //$NON-NLS-1$ |
| ClassloaderStats.loadedBundle(getClassloaderId(), new ResourceBundleStats(getClassloaderId(), name, result)); |
| } |
| } |
| return result; |
| } |
| protected void findClassPathEntry(ArrayList result, String entry, AbstractBundleData bundledata, ProtectionDomain domain) { |
| String var = hasPrefix(entry); |
| if (var == null) { |
| super.findClassPathEntry(result, entry, bundledata, domain); |
| return; |
| } |
| if (var.equals("ws")) { //$NON-NLS-1$ |
| super.findClassPathEntry(result, "ws/" + System.getProperties().getProperty("osgi.ws") + entry.substring(4), bundledata, domain); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| return; |
| } |
| if (var.equals("os")) { //$NON-NLS-1$ |
| super.findClassPathEntry(result, "os/" + System.getProperties().getProperty("osgi.os") + entry.substring(4), bundledata, domain); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| return; |
| } |
| if (var.equals("nl")) { //$NON-NLS-1$ |
| entry = entry.substring(4); |
| for (int i = 0; i < NL_JAR_VARIANTS.length; i++) { |
| if (addClassPathEntry(result, "nl/" + NL_JAR_VARIANTS[i] + entry, bundledata, domain)) //$NON-NLS-1$ //$NON-NLS-2$ |
| return; |
| } |
| // is we are not in development mode, post some framework errors. |
| if (!DevClassPathHelper.inDevelopmentMode()) { |
| BundleException be = new BundleException(Msg.formatter.getString("BUNDLE_CLASSPATH_ENTRY_NOT_FOUND_EXCEPTION", entry, hostdata.getLocation())); //$NON-NLS-1$ |
| bundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundledata.getBundle(), be); |
| } |
| } |
| } |
| private static String[] buildNLJarVariants(String nl) { |
| ArrayList result = new ArrayList(); |
| nl = nl.replace('_', '/'); |
| while (nl.length() > 0) { |
| result.add("nl/" + nl + "/"); //$NON-NLS-1$ //$NON-NLS-2$ |
| int i = nl.lastIndexOf('/'); //$NON-NLS-1$ |
| nl = (i < 0) ? "" : nl.substring(0, i); //$NON-NLS-1$ |
| } |
| result.add(""); //$NON-NLS-1$ |
| return (String[]) result.toArray(new String[result.size()]); |
| } |
| //return a String representing the string found between the $s |
| private String hasPrefix(String libPath) { |
| if (libPath.startsWith("$ws$")) //$NON-NLS-1$ |
| return "ws"; //$NON-NLS-1$ |
| if (libPath.startsWith("$os$")) //$NON-NLS-1$ |
| return "os"; //$NON-NLS-1$ |
| if (libPath.startsWith("$nl$")) //$NON-NLS-1$ |
| return "nl"; //$NON-NLS-1$ |
| return null; |
| } |
| /** |
| * Override to create EclipseClasspathEntry objects. EclipseClasspathEntry |
| * allows access to the manifest file for the classpath entry. |
| */ |
| protected ClasspathEntry createClassPathEntry(BundleFile bundlefile, ProtectionDomain domain) { |
| return new EclipseClasspathEntry(bundlefile, domain); |
| } |
| /** |
| * A ClasspathEntry that has a manifest associated with it. |
| */ |
| protected class EclipseClasspathEntry extends ClasspathEntry { |
| Manifest mf; |
| boolean initMF = false; |
| protected EclipseClasspathEntry(BundleFile bundlefile, ProtectionDomain domain) { |
| super(bundlefile, domain); |
| } |
| public Manifest getManifest() { |
| if (initMF) |
| return mf; |
| |
| BundleEntry mfEntry = getBundleFile().getEntry(org.eclipse.osgi.framework.internal.core.Constants.OSGI_BUNDLE_MANIFEST); |
| if (mfEntry != null) |
| try { |
| InputStream manIn = mfEntry.getInputStream(); |
| mf = new Manifest(manIn); |
| manIn.close(); |
| } catch (IOException e) { |
| // do nothing |
| } |
| initMF = true; |
| return mf; |
| } |
| } |
| } |