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<State> alreadyActive = EnumSet.of(State.ACTIVE, State.STOPPING, State.UNINSTALLED);
private static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
private final ThreadLocal<String> initiatingClassName = new ThreadLocal<>();
private final ThreadLocal<Deque<ClasspathManager>> activationStack = new ThreadLocal<>();
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 (alreadyActive.contains(module.getState()))
return;
if (!shouldActivateFor(name, module, revision, manager))
return;
Deque<ClasspathManager> stack = activationStack.get();
if (stack == null) {
stack = new ArrayDeque<>(6);
activationStack.set(stack);
}
if (!stack.contains(manager)) {
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;
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;
}
long startTime = System.currentTimeMillis();
Module m = managerElement.getGeneration().getRevision().getRevisions().getModule();
try {
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()) {
return true;
}
if (State.RESOLVED.equals(module.getState())) {
if (container.getConfiguration().throwErrorOnFailedStart) {
ClassNotFoundException error = errors.get(manager);
if (error != null)
throw error;
}
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);
if (excludes == null && includes == null)
return true;
int dotPosition = className.lastIndexOf('.');
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;
}
}