blob: 1902bdaf1e9599ce2a3b5c0d400c87af64fa7af1 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}
}