blob: d9a4df652470a0ec6fbcd65da1328705065c5742 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2009 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.core.runtime.internal.adaptor;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import org.eclipse.osgi.baseadaptor.*;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingStatsHook;
import org.eclipse.osgi.baseadaptor.loader.ClasspathEntry;
import org.eclipse.osgi.baseadaptor.loader.ClasspathManager;
import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
import org.eclipse.osgi.framework.adaptor.StatusException;
import org.eclipse.osgi.framework.debug.Debug;
import org.eclipse.osgi.framework.internal.core.*;
import org.eclipse.osgi.framework.internal.core.Constants;
import org.eclipse.osgi.framework.log.FrameworkLog;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.StateHelper;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
public class EclipseLazyStarter implements ClassLoadingStatsHook, AdaptorHook, HookConfigurator {
private static final boolean throwErrorOnFailedStart = "true".equals(FrameworkProperties.getProperty("osgi.compatibility.errorOnFailedStart", "true")); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
private BaseAdaptor adaptor;
// holds the current activation trigger class and the ClasspathManagers that need to be activated
private ThreadLocal activationStack = new ThreadLocal();
// used to store exceptions that occurred while activating a bundle
// keyed by ClasspathManager->Exception
// WeakHashMap is used to prevent pinning the ClasspathManager objects.
private final Map errors = Collections.synchronizedMap(new WeakHashMap());
public void preFindLocalClass(String name, ClasspathManager manager) throws ClassNotFoundException {
AbstractBundle bundle = (AbstractBundle) manager.getBaseData().getBundle();
// If the bundle is active, uninstalled or stopping then the bundle has already
// been initialized (though it may have been destroyed) so just return the class.
if ((bundle.getState() & (Bundle.ACTIVE | Bundle.UNINSTALLED | Bundle.STOPPING)) != 0)
return;
EclipseStorageHook storageHook = (EclipseStorageHook) manager.getBaseData().getStorageHook(EclipseStorageHook.KEY);
// The bundle is not active and does not require activation, just return the class
if (!shouldActivateFor(name, manager.getBaseData(), storageHook, manager))
return;
ArrayList stack = (ArrayList) activationStack.get();
if (stack == null) {
stack = new ArrayList(6);
activationStack.set(stack);
}
// the first element in the stack is the name of the trigger class,
// each element after the trigger class is a classpath manager
// that must be activated after the trigger class has been defined (see postFindLocalClass)
int size = stack.size();
if (size > 1) {
for (int i = size - 1; i >= 1; i--)
if (manager == stack.get(i))
// the manager is already on the stack in which case we are already in the process of loading the trigger class
return;
}
Thread threadChangingState = bundle.getStateChanging();
if (bundle.getState() == Bundle.STARTING && threadChangingState == Thread.currentThread())
return; // this thread is starting the bundle already
if (size == 0)
stack.add(name);
stack.add(manager);
}
public void postFindLocalClass(String name, Class clazz, ClasspathManager manager) throws ClassNotFoundException {
ArrayList stack = (ArrayList) activationStack.get();
if (stack == null)
return;
int size = stack.size();
if (size <= 1 || stack.get(0) != name)
return;
// if we have a stack we must clear it even if (clazz == null)
ClasspathManager[] managers = null;
managers = new ClasspathManager[size - 1];
for (int i = 1; i < size; i++)
managers[i - 1] = (ClasspathManager) stack.get(i);
stack.clear();
if (clazz == null)
return;
for (int i = managers.length - 1; i >= 0; i--) {
if (errors.get(managers[i]) != null) {
if (throwErrorOnFailedStart)
throw (TerminatingClassNotFoundException) errors.get(managers[i]);
continue;
}
// The bundle must be started.
// Note that another thread may already be starting this bundle;
// In this case we will timeout after 5 seconds and record the BundleException
try {
// do not persist the start of this bundle
managers[i].getBaseClassLoader().getDelegate().setLazyTrigger();
} catch (BundleException e) {
Bundle bundle = managers[i].getBaseData().getBundle();
Throwable cause = e.getCause();
if (cause != null && cause instanceof StatusException) {
StatusException status = (StatusException) cause;
if ((status.getStatusCode() & StatusException.CODE_ERROR) == 0) {
if (status.getStatus() instanceof Thread) {
String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_CONCURRENT_STARTUP, new Object[] {Thread.currentThread(), name, status.getStatus(), bundle, new Integer(5000)});
adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, e, null));
}
continue;
}
}
String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_ACTIVATION, bundle.getSymbolicName(), Long.toString(bundle.getBundleId()));
TerminatingClassNotFoundException error = new TerminatingClassNotFoundException(message, e);
errors.put(managers[i], error);
if (throwErrorOnFailedStart) {
adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null));
throw error;
}
adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, new BundleException(message, e));
}
}
}
public void preFindLocalResource(String name, ClasspathManager manager) {
// do nothing
}
public void postFindLocalResource(String name, URL resource, ClasspathManager manager) {
// do nothing
}
public void recordClassDefine(String name, Class clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
// do nothing
}
private boolean shouldActivateFor(String className, BaseData bundledata, EclipseStorageHook storageHook, ClasspathManager manager) throws ClassNotFoundException {
if (!isLazyStartable(className, bundledata, storageHook))
return false;
//if (manager.getBaseClassLoader().getDelegate().isLazyTriggerSet())
// return false;
// Don't activate non-starting bundles
if (bundledata.getBundle().getState() == Bundle.RESOLVED) {
if (throwErrorOnFailedStart) {
TerminatingClassNotFoundException error = (TerminatingClassNotFoundException) errors.get(manager);
if (error != null)
throw error;
}
return (bundledata.getStatus() & Constants.BUNDLE_STARTED) != 0;
}
return true;
}
private boolean isLazyStartable(String className, BaseData bundledata, EclipseStorageHook storageHook) {
if (storageHook == null)
return false;
boolean lazyStart = storageHook.isLazyStart();
String[] excludes = storageHook.getLazyStartExcludes();
String[] includes = storageHook.getLazyStartIncludes();
// no exceptions, it is easy to figure it out
if (excludes == null && includes == null)
return lazyStart;
// 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 lazyStart;
String packageName = className.substring(0, dotPosition);
if (lazyStart)
return ((includes == null || contains(includes, packageName)) && (excludes == null || !contains(excludes, packageName)));
return (excludes != null && contains(excludes, packageName));
}
private boolean contains(String[] array, String element) {
for (int i = 0; i < array.length; i++)
if (array[i].equals(element))
return true;
return false;
}
public void addHooks(HookRegistry hookRegistry) {
hookRegistry.addClassLoadingStatsHook(this);
hookRegistry.addAdaptorHook(this);
}
public void addProperties(Properties properties) {
// do nothing
}
public FrameworkLog createFrameworkLog() {
// do nothing
return null;
}
public void frameworkStart(BundleContext context) throws BundleException {
// nothing
}
public void frameworkStop(BundleContext context) throws BundleException {
// nothing
}
public void frameworkStopping(BundleContext context) {
if (!Debug.DEBUG || !Debug.DEBUG_ENABLED)
return;
BundleDescription[] allBundles = adaptor.getState().getResolvedBundles();
StateHelper stateHelper = adaptor.getPlatformAdmin().getStateHelper();
Object[][] cycles = stateHelper.sortBundles(allBundles);
logCycles(cycles);
}
public void handleRuntimeError(Throwable error) {
// do nothing
}
public void initialize(BaseAdaptor baseAdaptor) {
this.adaptor = baseAdaptor;
}
public URLConnection mapLocationToURLConnection(String location) throws IOException {
// do nothing
return null;
}
private void logCycles(Object[][] cycles) {
// log cycles
if (cycles.length > 0) {
StringBuffer cycleText = new StringBuffer("["); //$NON-NLS-1$
for (int i = 0; i < cycles.length; i++) {
cycleText.append('[');
for (int j = 0; j < cycles[i].length; j++) {
cycleText.append(((BundleDescription) cycles[i][j]).getSymbolicName());
cycleText.append(',');
}
cycleText.insert(cycleText.length() - 1, ']');
}
cycleText.setCharAt(cycleText.length() - 1, ']');
String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_BUNDLESTOPPER_CYCLES_FOUND, cycleText);
FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, null, null);
adaptor.getFrameworkLog().log(entry);
}
}
private static class TerminatingClassNotFoundException extends ClassNotFoundException implements StatusException {
private static final long serialVersionUID = -6730732895632169173L;
private Object cause;
public TerminatingClassNotFoundException(String message, Throwable cause) {
super(message, cause);
this.cause = cause;
}
public Object getStatus() {
return cause;
}
public int getStatusCode() {
return StatusException.CODE_ERROR;
}
}
}