blob: 26edbbac67c1875f842adae5dbb8ee5d46bd4922 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2017 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.hooks;
import java.security.AccessController;
import java.util.*;
import org.eclipse.osgi.container.*;
import org.eclipse.osgi.container.Module.StartOptions;
import org.eclipse.osgi.container.Module.State;
import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.framework.util.SecureAction;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook;
import org.eclipse.osgi.internal.loader.classpath.ClasspathManager;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
public class EclipseLazyStarter extends ClassLoaderHook {
private static final EnumSet<State> alreadyActive = EnumSet.of(State.ACTIVE, State.STOPPING, State.UNINSTALLED);
private static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
// holds the initiating class name
private final ThreadLocal<String> initiatingClassName = new ThreadLocal<>();
// holds the ClasspathManagers that need to be activated
private final ThreadLocal<Deque<ClasspathManager>> 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<ClasspathManager, ClassNotFoundException> errors = Collections.synchronizedMap(new WeakHashMap<ClasspathManager, ClassNotFoundException>());
private final EquinoxContainer container;
public EclipseLazyStarter(EquinoxContainer container) {
this.container = container;
}
@Override
public void preFindLocalClass(String name, ClasspathManager manager) throws ClassNotFoundException {
if (initiatingClassName.get() == null) {
initiatingClassName.set(name);
}
ModuleRevision revision = manager.getGeneration().getRevision();
Module module = revision.getRevisions().getModule();
// 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 (alreadyActive.contains(module.getState()))
return;
// The bundle is not active and does not require activation, just return the class
if (!shouldActivateFor(name, module, revision, manager))
return;
Deque<ClasspathManager> stack = activationStack.get();
if (stack == null) {
stack = new ArrayDeque<>(6);
activationStack.set(stack);
}
// each element is a classpath manager that must be activated after
// the initiating class has been defined (see postFindLocalClass)
if (!stack.contains(manager)) {
// only add the manager if it has not been added already
stack.addFirst(manager);
}
}
@Override
public void postFindLocalClass(String name, Class<?> clazz, ClasspathManager manager) throws ClassNotFoundException {
if (initiatingClassName.get() != name)
return;
initiatingClassName.set(null);
Deque<ClasspathManager> stack = activationStack.get();
if (stack == null || stack.isEmpty())
return;
// if we have a stack we must clear it even if (clazz == null)
List<ClasspathManager> managers = new ArrayList<>(stack);
stack.clear();
if (clazz == null)
return;
for (ClasspathManager managerElement : managers) {
if (errors.get(managerElement) != null) {
if (container.getConfiguration().throwErrorOnFailedStart)
throw errors.get(managerElement);
continue;
}
// The bundle must be started.
// Note that another thread may already be starting this bundle;
// In this case we will timeout after a default of 5 seconds and record the BundleException
long startTime = System.currentTimeMillis();
Module m = managerElement.getGeneration().getRevision().getRevisions().getModule();
try {
// do not persist the start of this bundle
secureAction.start(m, StartOptions.LAZY_TRIGGER);
} catch (BundleException e) {
Bundle bundle = managerElement.getGeneration().getRevision().getBundle();
if (e.getType() == BundleException.STATECHANGE_ERROR) {
String message = NLS.bind(Msg.ECLIPSE_CLASSLOADER_CONCURRENT_STARTUP, new Object[] {Thread.currentThread(), name, m.getStateChangeOwner(), bundle, new Long(System.currentTimeMillis() - startTime)});
container.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.WARNING, message, e);
continue;
}
String message = NLS.bind(Msg.ECLIPSE_CLASSLOADER_ACTIVATION, bundle.getSymbolicName(), Long.toString(bundle.getBundleId()));
ClassNotFoundException error = new ClassNotFoundException(message, e);
errors.put(managerElement, error);
if (container.getConfiguration().throwErrorOnFailedStart) {
container.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, message, e, null);
throw error;
}
container.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, new BundleException(message, e));
}
}
}
private boolean shouldActivateFor(String className, Module module, ModuleRevision revision, ClasspathManager manager) throws ClassNotFoundException {
State state = module.getState();
if (!State.LAZY_STARTING.equals(state)) {
if (State.STARTING.equals(state) && manager.getClassLoader().getBundleLoader().isTriggerSet()) {
// the trigger has been set but we are waiting for the activation to complete
return true;
}
// Don't activate non-starting bundles
if (State.RESOLVED.equals(module.getState())) {
// handle the resolved case where a previous error occurred
if (container.getConfiguration().throwErrorOnFailedStart) {
ClassNotFoundException error = errors.get(manager);
if (error != null)
throw error;
}
// The module is persistently started and has the lazy activation policy but has not entered the LAZY_STARTING state
// There are 2 cases where this can happen
// 1) The start-level thread has not gotten to transitioning the bundle to LAZY_STARTING yet
// 2) The bundle is marked for eager activation and the start-level thread has not activated it yet
// In both cases we need to fire the lazy start trigger to activate the bundle if the start-level is met
return module.isPersistentlyStarted() && isLazyStartable(className, revision);
}
return false;
}
return isLazyStartable(className, revision);
}
private boolean isLazyStartable(String className, ModuleRevision revision) {
if (!revision.hasLazyActivatePolicy()) {
return false;
}
List<ModuleCapability> moduleDatas = revision.getModuleCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE);
if (moduleDatas.isEmpty()) {
return false;
}
Map<String, Object> moduleDataAttrs = moduleDatas.get(0).getAttributes();
@SuppressWarnings("unchecked")
List<String> excludes = (List<String>) moduleDataAttrs.get(EquinoxModuleDataNamespace.CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE);
@SuppressWarnings("unchecked")
List<String> includes = (List<String>) moduleDataAttrs.get(EquinoxModuleDataNamespace.CAPABILITY_LAZY_INCLUDE_ATTRIBUTE);
// no exceptions, it is easy to figure it out
if (excludes == null && includes == null)
return true;
// 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 true;
String packageName = className.substring(0, dotPosition);
return ((includes == null || includes.contains(packageName)) && (excludes == null || !excludes.contains(packageName)));
}
@Override
public boolean isProcessClassRecursionSupported() {
return true;
}
}