Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 8339703cd13282fee56d038629e52fd7c733bc80 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/*******************************************************************************
 * Copyright (c) 2006, 2017 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<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;
	}

}

Back to the top