/******************************************************************************* * 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; } }