org.eclipse.osgi.internal.hooks.EclipseLazyStarter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aspectjtools Show documentation
Show all versions of aspectjtools Show documentation
Tools from the AspectJ project
/*******************************************************************************
* Copyright (c) 2006, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.hooks;
import java.security.AccessController;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.eclipse.osgi.container.Module;
import org.eclipse.osgi.container.Module.StartOptions;
import org.eclipse.osgi.container.Module.State;
import org.eclipse.osgi.container.ModuleCapability;
import org.eclipse.osgi.container.ModuleRevision;
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.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkEvent;
public class EclipseLazyStarter extends ClassLoaderHook {
private static final EnumSet 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 initiatingClassName = new ThreadLocal<>();
// holds the ClasspathManagers that need to be activated
private final 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());
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 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 stack = activationStack.get();
if (stack == null || stack.isEmpty())
return;
// if we have a stack we must clear it even if (clazz == null)
List 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, Long.valueOf(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 moduleDatas = revision.getModuleCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE);
if (moduleDatas.isEmpty()) {
return false;
}
Map moduleDataAttrs = moduleDatas.get(0).getAttributes();
@SuppressWarnings("unchecked")
List excludes = (List) moduleDataAttrs.get(EquinoxModuleDataNamespace.CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE);
@SuppressWarnings("unchecked")
List includes = (List) 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;
}
}