1 /*******************************************************************************
2  * Copyright (c) 2005, 2019 Cognos Incorporated, IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     Cognos Incorporated - initial API and implementation
13  *     IBM Corporation - bug fixes and enhancements
14  *     Code 9 - bug fixes and enhancements
15  *******************************************************************************/
16 package org.eclipse.equinox.servletbridge;
17 
18 import java.io.*;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.security.*;
24 import java.util.*;
25 import java.util.jar.*;
26 import javax.servlet.ServletConfig;
27 import javax.servlet.ServletContext;
28 
29 /**
30  * The FrameworkLauncher provides the logic to:
31  * 1) init
32  * 2) deploy
33  * 3) start
34  * 4) stop
35  * 5) undeploy
36  * 6) destroy
37  * an instance of the OSGi framework.
38  * These 6 methods are provided to help manage the life-cycle and are called from outside this
39  * class by the BridgeServlet. To create an extended FrameworkLauncher over-ride these methods to allow
40  * custom behavior.
41  */
42 public class FrameworkLauncher {
43 
44 	private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$
45 	private static final String CONFIG_INI = "config.ini"; //$NON-NLS-1$
46 	private static final String DOT_JAR = ".jar"; //$NON-NLS-1$
47 	private static final String WS_DELIM = " \t\n\r\f"; //$NON-NLS-1$
48 	protected static final String FILE_SCHEME = "file:"; //$NON-NLS-1$
49 	protected static final String FRAMEWORK_BUNDLE_NAME = "org.eclipse.osgi"; //$NON-NLS-1$
50 	protected static final String STARTER = "org.eclipse.core.runtime.adaptor.EclipseStarter"; //$NON-NLS-1$
51 	protected static final String NULL_IDENTIFIER = "@null"; //$NON-NLS-1$
52 	protected static final String OSGI_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$
53 	protected static final String OSGI_FRAMEWORK_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$
54 	protected static final String OSGI_INSTANCE_AREA = "osgi.instance.area"; //$NON-NLS-1$
55 	protected static final String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; //$NON-NLS-1$
56 	protected static final String OSGI_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$
57 	protected static final String OSGI_FORCED_RESTART = "osgi.forcedRestart"; //$NON-NLS-1$
58 	protected static final String RESOURCE_BASE = "/WEB-INF/"; //$NON-NLS-1$
59 	protected static final String ECLIPSE = "eclipse/"; //$NON-NLS-1$
60 	protected static final String LAUNCH_INI = "launch.ini"; //$NON-NLS-1$
61 
62 	private static final String EXTENSIONBUNDLE_DEFAULT_BSN = "org.eclipse.equinox.servletbridge.extensionbundle"; //$NON-NLS-1$
63 	private static final String EXTENSIONBUNDLE_DEFAULT_VERSION = "1.3.0"; //$NON-NLS-1$
64 	private static final String MANIFEST_VERSION = "Manifest-Version"; //$NON-NLS-1$
65 	private static final String BUNDLE_MANIFEST_VERSION = "Bundle-ManifestVersion"; //$NON-NLS-1$
66 	private static final String BUNDLE_NAME = "Bundle-Name"; //$NON-NLS-1$
67 	private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName"; //$NON-NLS-1$
68 	private static final String BUNDLE_VERSION = "Bundle-Version"; //$NON-NLS-1$
69 	private static final String FRAGMENT_HOST = "Fragment-Host"; //$NON-NLS-1$
70 	private static final String EXPORT_PACKAGE = "Export-Package"; //$NON-NLS-1$
71 
72 	private static final String CONFIG_COMMANDLINE = "commandline"; //$NON-NLS-1$
73 	private static final String CONFIG_EXTENDED_FRAMEWORK_EXPORTS = "extendedFrameworkExports"; //$NON-NLS-1$
74 	private static final String CONFIG_OVERRIDE_AND_REPLACE_EXTENSION_BUNDLE = "overrideAndReplaceExtensionBundle"; //$NON-NLS-1$
75 
76 	static final PermissionCollection allPermissions = new PermissionCollection() {
77 		private static final long serialVersionUID = 482874725021998286L;
78 		// The AllPermission permission
79 		Permission allPermission = new AllPermission();
80 
81 		// A simple PermissionCollection that only has AllPermission
82 		@Override
83 		public void add(Permission permission) {
84 			// do nothing
85 		}
86 
87 		@Override
88 		public boolean implies(Permission permission) {
89 			return true;
90 		}
91 
92 		@Override
93 		public Enumeration<Permission> elements() {
94 			return new Enumeration<Permission>() {
95 				int cur = 0;
96 
97 				@Override
98 				public boolean hasMoreElements() {
99 					return cur < 1;
100 				}
101 
102 				@Override
103 				public Permission nextElement() {
104 					if (cur == 0) {
105 						cur = 1;
106 						return allPermission;
107 					}
108 					throw new NoSuchElementException();
109 				}
110 			};
111 		}
112 	};
113 
114 	static {
115 		// We do this to ensure the anonymous Enumeration class in allPermissions is pre-loaded
116 		if (allPermissions.elements() == null)
IllegalStateException()117 			throw new IllegalStateException();
118 	}
119 
120 	protected ServletConfig config;
121 	protected ServletContext context;
122 	protected String resourceBase;
123 	private File platformDirectory;
124 	private ClassLoader frameworkContextClassLoader;
125 	private CloseableURLClassLoader frameworkClassLoader;
126 
init(ServletConfig servletConfig)127 	void init(ServletConfig servletConfig) {
128 		config = servletConfig;
129 		context = servletConfig.getServletContext();
130 		initResourceBase();
131 		init();
132 	}
133 
134 	/**
135 	 * try to find the resource base for this webapp by looking for the launcher initialization file.
136 	 */
initResourceBase()137 	protected void initResourceBase() {
138 		try {
139 			if (context.getResource(RESOURCE_BASE + LAUNCH_INI) != null) {
140 				resourceBase = RESOURCE_BASE;
141 				return;
142 			}
143 			if (context.getResource(RESOURCE_BASE + ECLIPSE + LAUNCH_INI) != null) {
144 				resourceBase = RESOURCE_BASE + ECLIPSE;
145 				return;
146 			}
147 		} catch (MalformedURLException e) {
148 			// ignore
149 		}
150 		// If things don't work out, use the default resource base
151 		resourceBase = RESOURCE_BASE;
152 	}
153 
154 	/**
155 	 * init is the first method called on the FrameworkLauncher and can be used for any initial setup.
156 	 * The default behavior is to do nothing.
157 	 */
init()158 	public void init() {
159 		// do nothing for now
160 	}
161 
162 	/**
163 	 * destroy is the last method called on the FrameworkLauncher and can be used for any final cleanup.
164 	 * The default behavior is to do nothing.
165 	 */
destroy()166 	public void destroy() {
167 		// do nothing for now
168 	}
169 
170 	/**
171 	 * deploy is used to move the OSGi framework libraries into a location suitable for execution.
172 	 * The default behavior is to copy the contents of the webapp's WEB-INF/eclipse directory
173 	 * to the webapp's temp directory.
174 	 */
deploy()175 	public synchronized void deploy() {
176 		if (platformDirectory != null) {
177 			context.log("Framework is already deployed"); //$NON-NLS-1$
178 			return;
179 		}
180 
181 		File servletTemp = (File) context.getAttribute("javax.servlet.context.tempdir"); //$NON-NLS-1$
182 		platformDirectory = new File(servletTemp, "eclipse"); //$NON-NLS-1$
183 		if (!platformDirectory.exists()) {
184 			platformDirectory.mkdirs();
185 		}
186 
187 		copyResource(resourceBase + "configuration/", new File(platformDirectory, "configuration")); //$NON-NLS-1$ //$NON-NLS-2$
188 		copyResource(resourceBase + "features/", new File(platformDirectory, "features")); //$NON-NLS-1$ //$NON-NLS-2$
189 		File plugins = new File(platformDirectory, "plugins"); //$NON-NLS-1$
190 		copyResource(resourceBase + "plugins/", plugins); //$NON-NLS-1$
191 		copyResource(resourceBase + "p2/", new File(platformDirectory, "p2")); //$NON-NLS-1$ //$NON-NLS-2$
192 		deployExtensionBundle(plugins);
193 		copyResource(resourceBase + ".eclipseproduct", new File(platformDirectory, ".eclipseproduct")); //$NON-NLS-1$ //$NON-NLS-2$
194 	}
195 
196 	/**
197 	 * deployExtensionBundle will generate the Servletbridge extensionbundle if it is not already present in the platform's
198 	 * plugin directory. By default it exports "org.eclipse.equinox.servletbridge" and a versioned export of the Servlet API.
199 	 * Additional exports can be added by using the "extendedFrameworkExports" initial-param in the ServletConfig
200 	 */
deployExtensionBundle(File plugins)201 	private void deployExtensionBundle(File plugins) {
202 		// we might want to parameterize the extension bundle BSN in the future
203 		final String extensionBundleBSN = EXTENSIONBUNDLE_DEFAULT_BSN;
204 		File extensionBundleFile = findExtensionBundleFile(plugins, extensionBundleBSN);
205 
206 		if (extensionBundleFile == null)
207 			generateExtensionBundle(plugins, extensionBundleBSN, EXTENSIONBUNDLE_DEFAULT_VERSION);
208 		else if (Boolean.valueOf(config.getInitParameter(CONFIG_OVERRIDE_AND_REPLACE_EXTENSION_BUNDLE)).booleanValue()) {
209 			String extensionBundleVersion = findExtensionBundleVersion(extensionBundleFile, extensionBundleBSN);
210 			if (extensionBundleFile.isDirectory()) {
211 				deleteDirectory(extensionBundleFile);
212 			} else {
213 				extensionBundleFile.delete();
214 			}
215 			generateExtensionBundle(plugins, extensionBundleBSN, extensionBundleVersion);
216 		} else {
217 			processExtensionBundle(extensionBundleFile);
218 		}
219 	}
220 
findExtensionBundleFile(File plugins, final String extensionBundleBSN)221 	private File findExtensionBundleFile(File plugins, final String extensionBundleBSN) {
222 		FileFilter extensionBundleFilter = new FileFilter() {
223 			@Override
224 			public boolean accept(File candidate) {
225 				return candidate.getName().startsWith(extensionBundleBSN + "_"); //$NON-NLS-1$
226 			}
227 		};
228 		File[] extensionBundles = plugins.listFiles(extensionBundleFilter);
229 		if (extensionBundles.length == 0)
230 			return null;
231 
232 		if (extensionBundles.length > 1) {
233 			for (int i = 1; i < extensionBundles.length; i++) {
234 				if (extensionBundles[i].isDirectory()) {
235 					deleteDirectory(extensionBundles[i]);
236 				} else {
237 					extensionBundles[i].delete();
238 				}
239 			}
240 		}
241 		return extensionBundles[0];
242 	}
243 
findExtensionBundleVersion(File extensionBundleFile, String extensionBundleBSN)244 	private String findExtensionBundleVersion(File extensionBundleFile, String extensionBundleBSN) {
245 		String fileName = extensionBundleFile.getName();
246 		if (fileName.endsWith(DOT_JAR)) {
247 			return fileName.substring(extensionBundleBSN.length() + 1, fileName.length() - DOT_JAR.length());
248 		}
249 		return fileName.substring(extensionBundleBSN.length() + 1);
250 	}
251 
generateExtensionBundle(File plugins, String extensionBundleBSN, String extensionBundleVersion)252 	private void generateExtensionBundle(File plugins, String extensionBundleBSN, String extensionBundleVersion) {
253 		Manifest mf = new Manifest();
254 		Attributes attribs = mf.getMainAttributes();
255 		attribs.putValue(MANIFEST_VERSION, "1.0"); //$NON-NLS-1$
256 		attribs.putValue(BUNDLE_MANIFEST_VERSION, "2"); //$NON-NLS-1$
257 		attribs.putValue(BUNDLE_NAME, "Servletbridge Extension Bundle"); //$NON-NLS-1$
258 		attribs.putValue(BUNDLE_SYMBOLIC_NAME, extensionBundleBSN);
259 		attribs.putValue(BUNDLE_VERSION, extensionBundleVersion);
260 		attribs.putValue(FRAGMENT_HOST, "system.bundle; extension:=framework"); //$NON-NLS-1$
261 
262 		String packageExports = null;
263 		if (context.getMajorVersion() > 3) {
264 			// we really have no idea what the packages or versions are, it all just a guess ...
265 			String servletVersion = context.getMajorVersion() + "." + context.getMinorVersion(); //$NON-NLS-1$
266 			packageExports = "org.eclipse.equinox.servletbridge; version=1.1" + //$NON-NLS-1$
267 					", javax.servlet; version=" + servletVersion + //$NON-NLS-1$
268 					", javax.servlet.annotation; version=" + servletVersion + //$NON-NLS-1$
269 					", javax.servlet.descriptor; version=" + servletVersion + //$NON-NLS-1$
270 					", javax.servlet.http; version=" + servletVersion + //$NON-NLS-1$
271 					", javax.servlet.resources; version=" + servletVersion; //$NON-NLS-1$
272 		} else if (context.getMajorVersion() == 3) {
273 			// We know spec version 3.0 corresponds to package version 2.6
274 			// we are guessing future 3.x spec versions will increment package versions minor, so ...
275 			String servletVersion = (context.getMajorVersion() - 1) + "." + (context.getMinorVersion() + 6); //$NON-NLS-1$
276 			String specVersion = context.getMajorVersion() + "." + context.getMinorVersion(); //$NON-NLS-1$
277 			packageExports = "org.eclipse.equinox.servletbridge; version=1.1" + //$NON-NLS-1$
278 					", javax.servlet; version=" + servletVersion + //$NON-NLS-1$
279 					", javax.servlet; version=" + specVersion + //$NON-NLS-1$
280 					", javax.servlet.annotation; version=" + servletVersion + //$NON-NLS-1$
281 					", javax.servlet.annotation; version=" + specVersion + //$NON-NLS-1$
282 					", javax.servlet.descriptor; version=" + servletVersion + //$NON-NLS-1$
283 					", javax.servlet.descriptor; version=" + specVersion + //$NON-NLS-1$
284 					", javax.servlet.http; version=" + servletVersion + //$NON-NLS-1$
285 					", javax.servlet.http; version=" + specVersion + //$NON-NLS-1$
286 					", javax.servlet.resources; version=" + servletVersion + //$NON-NLS-1$
287 					", javax.servlet.resources; version=" + specVersion; //$NON-NLS-1$
288 		} else {
289 			// We know spec version 2.x directly correspond to package versions
290 			String servletVersion = context.getMajorVersion() + "." + context.getMinorVersion(); //$NON-NLS-1$
291 			packageExports = "org.eclipse.equinox.servletbridge; version=1.1" + //$NON-NLS-1$
292 					", javax.servlet; version=" + servletVersion + //$NON-NLS-1$
293 					", javax.servlet.http; version=" + servletVersion + //$NON-NLS-1$
294 					", javax.servlet.resources; version=" + servletVersion; //$NON-NLS-1$
295 		}
296 
297 		String extendedExports = config.getInitParameter(CONFIG_EXTENDED_FRAMEWORK_EXPORTS);
298 		if (extendedExports != null && extendedExports.trim().length() != 0)
299 			packageExports += ", " + extendedExports; //$NON-NLS-1$
300 
301 		attribs.putValue(EXPORT_PACKAGE, packageExports);
302 		writeJarFile(new File(plugins, extensionBundleBSN + "_" + extensionBundleVersion + DOT_JAR), mf); //$NON-NLS-1$
303 	}
304 
processExtensionBundle(File extensionBundleFile)305 	private void processExtensionBundle(File extensionBundleFile) {
306 		String fileName = extensionBundleFile.getName();
307 		if (fileName.endsWith(DOT_JAR)) {
308 			Manifest mf = readJarFile(extensionBundleFile);
309 			if (mf == null)
310 				return;
311 			Attributes attributes = mf.getMainAttributes();
312 			String exportPackage = (String) attributes.remove(new Attributes.Name("X-Deploy-Export-Package")); //$NON-NLS-1$
313 			if (exportPackage != null) {
314 				attributes.putValue("Export-Package", exportPackage); //$NON-NLS-1$
315 				writeJarFile(extensionBundleFile, mf);
316 			}
317 		}
318 	}
319 
writeJarFile(File jarFile, Manifest mf)320 	private void writeJarFile(File jarFile, Manifest mf) {
321 		try {
322 			JarOutputStream jos = null;
323 			try {
324 				jos = new JarOutputStream(new FileOutputStream(jarFile), mf);
325 				jos.finish();
326 			} finally {
327 				if (jos != null)
328 					jos.close();
329 			}
330 		} catch (IOException e) {
331 			context.log("Error writing extension bundle", e); //$NON-NLS-1$
332 		}
333 	}
334 
readJarFile(File jarFile)335 	private Manifest readJarFile(File jarFile) {
336 		try {
337 			JarInputStream jis = null;
338 			try {
339 				jis = new JarInputStream(new FileInputStream(jarFile));
340 				return jis.getManifest();
341 			} finally {
342 				if (jis != null)
343 					jis.close();
344 			}
345 		} catch (IOException e) {
346 			context.log("Error reading extension bundle", e); //$NON-NLS-1$
347 		}
348 		return null;
349 	}
350 
351 	/** undeploy is the reverse operation of deploy and removes the OSGi framework libraries from their
352 	 * execution location. Typically this method will only be called if a manual undeploy is requested in the
353 	 * ServletBridge.
354 	 * By default, this method removes the OSGi install and also removes the workspace.
355 	 */
undeploy()356 	public synchronized void undeploy() {
357 		if (platformDirectory == null) {
358 			context.log("Undeploy unnecessary. - (not deployed)"); //$NON-NLS-1$
359 			return;
360 		}
361 
362 		if (frameworkClassLoader != null) {
363 			throw new IllegalStateException("Could not undeploy Framework - (not stopped)"); //$NON-NLS-1$
364 		}
365 
366 		deleteDirectory(new File(platformDirectory, "configuration")); //$NON-NLS-1$
367 		deleteDirectory(new File(platformDirectory, "features")); //$NON-NLS-1$
368 		deleteDirectory(new File(platformDirectory, "plugins")); //$NON-NLS-1$
369 		deleteDirectory(new File(platformDirectory, "workspace")); //$NON-NLS-1$
370 		deleteDirectory(new File(platformDirectory, "p2")); //$NON-NLS-1$
371 
372 		new File(platformDirectory, ".eclipseproduct").delete(); //$NON-NLS-1$
373 		platformDirectory = null;
374 	}
375 
376 	/** start is used to "start" a previously deployed OSGi framework
377 	 * The default behavior will read launcher.ini to create a set of initial properties and
378 	 * use the "commandline" configuration parameter to create the equivalent command line arguments
379 	 * available when starting Eclipse.
380 	 */
start()381 	public synchronized void start() {
382 		if (platformDirectory == null)
383 			throw new IllegalStateException("Could not start the Framework - (not deployed)"); //$NON-NLS-1$
384 
385 		if (frameworkClassLoader != null) {
386 			context.log("Framework is already started"); //$NON-NLS-1$
387 			return;
388 		}
389 
390 		Map<String, String> initialPropertyMap = buildInitialPropertyMap();
391 		String[] args = buildCommandLineArguments();
392 
393 		// Handle commandline -D properties
394 		for (String arg : args) {
395 			if (arg.startsWith("-D")) { //$NON-NLS-1$
396 				int equalsIndex = arg.indexOf('=');
397 				if (equalsIndex == -1) {
398 					initialPropertyMap.put(arg.substring(2), ""); //$NON-NLS-1$
399 				} else {
400 					String key = arg.substring(2, equalsIndex);
401 					String value = arg.substring(equalsIndex + 1);
402 					if (value.startsWith("\"") && value.endsWith("\"")) //$NON-NLS-1$//$NON-NLS-2$
403 						value = value.substring(1, value.length() - 1);
404 					setInitialProperty(initialPropertyMap, key, value);
405 				}
406 			}
407 		}
408 
409 		ClassLoader original = Thread.currentThread().getContextClassLoader();
410 		try {
411 			System.setProperty("osgi.framework.useSystemProperties", "false"); //$NON-NLS-1$ //$NON-NLS-2$
412 
413 			URL[] frameworkURLs = findFrameworkURLs(initialPropertyMap);
414 			frameworkClassLoader = new ChildFirstURLClassLoader(frameworkURLs, this.getClass().getClassLoader());
415 			Class<?> clazz = frameworkClassLoader.loadClass(STARTER);
416 
417 			Method setInitialProperties = clazz.getMethod("setInitialProperties", Map.class); //$NON-NLS-1$
418 			setInitialProperties.invoke(null, initialPropertyMap);
419 
420 			registerRestartHandler(clazz);
421 
422 			Method runMethod = clazz.getMethod("startup", String[].class, Runnable.class); //$NON-NLS-1$
423 			runMethod.invoke(null, args, null);
424 
425 			frameworkContextClassLoader = Thread.currentThread().getContextClassLoader();
426 		} catch (
427 
428 		InvocationTargetException ite) {
429 			Throwable t = ite.getTargetException();
430 			if (t == null)
431 				t = ite;
432 			context.log("Error while starting Framework", t); //$NON-NLS-1$
433 			throw new RuntimeException(t.getMessage());
434 		} catch (Exception e) {
435 			context.log("Error while starting Framework", e); //$NON-NLS-1$
436 			throw new RuntimeException(e.getMessage());
437 		} finally {
438 			Thread.currentThread().setContextClassLoader(original);
439 		}
440 	}
441 
findFrameworkURLs(Map<String, String> initialPropertyMap)442 	private URL[] findFrameworkURLs(Map<String, String> initialPropertyMap) {
443 		List<URL> frameworkURLs = new ArrayList<>();
444 		String installArea = initialPropertyMap.get(OSGI_INSTALL_AREA);
445 		if (installArea.startsWith(FILE_SCHEME)) {
446 			installArea = installArea.substring(FILE_SCHEME.length());
447 		}
448 		File installBase = new File(installArea);
449 
450 		// OSGi framework
451 		String osgiFramework = initialPropertyMap.get(OSGI_FRAMEWORK);
452 		File osgiFrameworkFile = null;
453 		if (osgiFramework == null) {
454 			// search for osgi.framework in osgi.install.area
455 			String path = new File(installBase, "plugins").toString(); //$NON-NLS-1$
456 			path = searchFor(FRAMEWORK_BUNDLE_NAME, path);
457 			if (path == null)
458 				throw new RuntimeException("Could not find framework"); //$NON-NLS-1$
459 
460 			osgiFrameworkFile = new File(path);
461 		} else {
462 			if (osgiFramework.startsWith(FILE_SCHEME)) {
463 				osgiFramework = osgiFramework.substring(FILE_SCHEME.length());
464 			}
465 			osgiFrameworkFile = new File(osgiFramework);
466 			if (!osgiFrameworkFile.isAbsolute())
467 				osgiFrameworkFile = new File(installBase, osgiFramework);
468 		}
469 
470 		try {
471 			URL frameworkURL = osgiFrameworkFile.toURL();
472 			frameworkURLs.add(frameworkURL);
473 			// ensure the framework URL is absolute
474 			initialPropertyMap.put(OSGI_FRAMEWORK, frameworkURL.toExternalForm());
475 		} catch (MalformedURLException e) {
476 			throw new RuntimeException("Could not find framework -- " + e.getMessage()); //$NON-NLS-1$
477 		}
478 
479 		// OSGi framework extensions
480 		String osgiFrameworkExtensions = initialPropertyMap.get(OSGI_FRAMEWORK_EXTENSIONS);
481 		if (osgiFrameworkExtensions != null) {
482 			StringTokenizer tokenizer = new StringTokenizer(osgiFrameworkExtensions, ","); //$NON-NLS-1$
483 			while (tokenizer.hasMoreTokens()) {
484 				String extension = tokenizer.nextToken().trim();
485 				if (extension.length() == 0)
486 					continue;
487 
488 				URL extensionURL = findExtensionURL(extension, osgiFrameworkFile);
489 				if (extensionURL != null) {
490 					frameworkURLs.add(extensionURL);
491 				}
492 			}
493 		}
494 		return frameworkURLs.toArray(new URL[frameworkURLs.size()]);
495 	}
496 
findExtensionURL(String extension, File osgiFrameworkFile)497 	private URL findExtensionURL(String extension, File osgiFrameworkFile) {
498 		File extensionFile = null;
499 		if (extension.startsWith(REFERENCE_SCHEME)) {
500 			extension = extension.substring(REFERENCE_SCHEME.length());
501 			if (!extension.startsWith(FILE_SCHEME))
502 				throw new RuntimeException("Non-file scheme for framework extension URL -- " + extension); //$NON-NLS-1$
503 			extension = extension.substring(FILE_SCHEME.length());
504 			extensionFile = new File(extension);
505 			if (!extensionFile.isAbsolute())
506 				extensionFile = new File(osgiFrameworkFile.getParentFile(), extension);
507 		} else {
508 			String fullExtensionPath = searchFor(extension, osgiFrameworkFile.getParent());
509 			if (fullExtensionPath == null)
510 				return null;
511 			extensionFile = new File(fullExtensionPath);
512 		}
513 
514 		try {
515 			return extensionFile.toURL();
516 		} catch (MalformedURLException e) {
517 			throw new RuntimeException("Could not find framework extension -- " + extensionFile.getAbsolutePath() + " : " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
518 		}
519 	}
520 
registerRestartHandler(Class<?> starterClazz)521 	private void registerRestartHandler(Class<?> starterClazz) throws IllegalAccessException, InvocationTargetException {
522 		Method registerFrameworkShutdownHandler = null;
523 		try {
524 			registerFrameworkShutdownHandler = starterClazz.getDeclaredMethod("internalAddFrameworkShutdownHandler", Runnable.class); //$NON-NLS-1$
525 			if (!registerFrameworkShutdownHandler.isAccessible()) {
526 				registerFrameworkShutdownHandler.setAccessible(true);
527 			}
528 			Runnable restartHandler = createRestartHandler(starterClazz);
529 			registerFrameworkShutdownHandler.invoke(null, restartHandler);
530 		} catch (NoSuchMethodException e) {
531 			// Ok. However we will not support restart events. Log this as info
532 			context.log(starterClazz.getName() + " does not support setting a shutdown handler. Restart handling is disabled."); //$NON-NLS-1$
533 			return;
534 		}
535 
536 	}
537 
createRestartHandler(Class<?> starterClazz)538 	private Runnable createRestartHandler(Class<?> starterClazz) throws NoSuchMethodException {
539 		final Method getProperty = starterClazz.getDeclaredMethod("getProperty", String.class); //$NON-NLS-1$
540 		if (!getProperty.isAccessible()) {
541 			getProperty.setAccessible(true);
542 		}
543 		Runnable restartHandler = new Runnable() {
544 			@Override
545 			public void run() {
546 				try {
547 					String forcedRestart = (String) getProperty.invoke(null, OSGI_FORCED_RESTART);
548 					if (Boolean.valueOf(forcedRestart).booleanValue()) {
549 						stop();
550 						start();
551 					}
552 				} catch (InvocationTargetException ite) {
553 					Throwable t = ite.getTargetException();
554 					if (t == null)
555 						t = ite;
556 					throw new RuntimeException(t.getMessage());
557 				} catch (Exception e) {
558 					throw new RuntimeException(e.getMessage());
559 				}
560 			}
561 		};
562 		return restartHandler;
563 	}
564 
565 	/** buildInitialPropertyMap create the initial set of properties from the contents of launch.ini
566 	 * and for a few other properties necessary to launch defaults are supplied if not provided.
567 	 * The value '@null' will set the map value to null.
568 	 * @return a map containing the initial properties
569 	 */
570 	@SuppressWarnings("rawtypes")
buildInitialPropertyMap()571 	protected Map<String, String> buildInitialPropertyMap() {
572 		Map<String, String> initialPropertyMap = new HashMap<>();
573 		Properties launchProperties = loadProperties(resourceBase + LAUNCH_INI);
574 		for (Map.Entry entry : launchProperties.entrySet()) {
575 			String key = (String) entry.getKey();
576 			String value = (String) entry.getValue();
577 			setInitialProperty(initialPropertyMap, key, value);
578 		}
579 
580 		try {
581 			// install.area if not specified
582 			if (initialPropertyMap.get(OSGI_INSTALL_AREA) == null)
583 				initialPropertyMap.put(OSGI_INSTALL_AREA, platformDirectory.toURL().toExternalForm());
584 
585 			// configuration.area if not specified
586 			if (initialPropertyMap.get(OSGI_CONFIGURATION_AREA) == null) {
587 				File configurationDirectory = new File(platformDirectory, "configuration"); //$NON-NLS-1$
588 				if (!configurationDirectory.exists()) {
589 					configurationDirectory.mkdirs();
590 				}
591 				initialPropertyMap.put(OSGI_CONFIGURATION_AREA, configurationDirectory.toURL().toExternalForm());
592 			}
593 
594 			// instance.area if not specified
595 			if (initialPropertyMap.get(OSGI_INSTANCE_AREA) == null) {
596 				File workspaceDirectory = new File(platformDirectory, "workspace"); //$NON-NLS-1$
597 				if (!workspaceDirectory.exists()) {
598 					workspaceDirectory.mkdirs();
599 				}
600 				initialPropertyMap.put(OSGI_INSTANCE_AREA, workspaceDirectory.toURL().toExternalForm());
601 			}
602 
603 			// read values from config.ini
604 			Properties configurationProperties = loadConfigurationFile(initialPropertyMap);
605 
606 			// osgi.framework if not specified
607 			if (initialPropertyMap.get(OSGI_FRAMEWORK) == null) {
608 				String osgiFramework = configurationProperties.getProperty(OSGI_FRAMEWORK);
609 				if (osgiFramework != null)
610 					initialPropertyMap.put(OSGI_FRAMEWORK, osgiFramework);
611 			}
612 
613 			// osgi.framework.extensions if not specified
614 			if (initialPropertyMap.get(OSGI_FRAMEWORK_EXTENSIONS) == null) {
615 				String osgiFrameworkExtensions = configurationProperties.getProperty(OSGI_FRAMEWORK_EXTENSIONS);
616 				if (osgiFrameworkExtensions != null)
617 					initialPropertyMap.put(OSGI_FRAMEWORK_EXTENSIONS, osgiFrameworkExtensions);
618 			}
619 
620 		} catch (MalformedURLException e) {
621 			throw new RuntimeException("Error establishing location"); //$NON-NLS-1$
622 		}
623 
624 		return initialPropertyMap;
625 	}
626 
setInitialProperty(Map<String, String> initialPropertyMap, String key, String value)627 	private void setInitialProperty(Map<String, String> initialPropertyMap, String key, String value) {
628 		if (key.endsWith("*")) { //$NON-NLS-1$
629 			if (value.equals(NULL_IDENTIFIER)) {
630 				clearPrefixedSystemProperties(key.substring(0, key.length() - 1), initialPropertyMap);
631 			}
632 		} else if (value.equals(NULL_IDENTIFIER))
633 			initialPropertyMap.put(key, null);
634 		else
635 			initialPropertyMap.put(key, value);
636 	}
637 
loadConfigurationFile(Map<String, String> initialPropertyMap)638 	private Properties loadConfigurationFile(Map<String, String> initialPropertyMap) {
639 		InputStream is = null;
640 		try {
641 			String installArea = initialPropertyMap.get(OSGI_INSTALL_AREA);
642 			if (installArea.startsWith(FILE_SCHEME)) {
643 				installArea = installArea.substring(FILE_SCHEME.length());
644 			}
645 			File installBase = new File(installArea);
646 
647 			String configurationArea = initialPropertyMap.get(OSGI_CONFIGURATION_AREA);
648 			if (configurationArea.startsWith(FILE_SCHEME)) {
649 				configurationArea = configurationArea.substring(FILE_SCHEME.length());
650 			}
651 			File configurationBase = new File(configurationArea);
652 			if (!configurationBase.isAbsolute())
653 				configurationBase = new File(installBase, configurationArea);
654 
655 			File configurationFile = new File(configurationBase, CONFIG_INI);
656 			if (!configurationFile.exists())
657 				return null;
658 
659 			Properties configProperties = new Properties();
660 			is = new BufferedInputStream(new FileInputStream(configurationFile));
661 			configProperties.load(is);
662 			return configProperties;
663 		} catch (Throwable t) {
664 			context.log("Error reading configuration file -- " + t.toString()); //$NON-NLS-1$
665 			return null;
666 		} finally {
667 			if (is != null)
668 				try {
669 					is.close();
670 				} catch (IOException e) {
671 					// unexpected
672 					e.printStackTrace();
673 				}
674 		}
675 	}
676 
677 	/**
678 	 * clearPrefixedSystemProperties clears System Properties by writing null properties in the targetPropertyMap that match a prefix
679 	 */
clearPrefixedSystemProperties(String prefix, Map<String, String> targetPropertyMap)680 	private static void clearPrefixedSystemProperties(String prefix, Map<String, String> targetPropertyMap) {
681 		for (Object key : System.getProperties().keySet()) {
682 			String propertyName = (String) key;
683 			if (propertyName.startsWith(prefix) && !targetPropertyMap.containsKey(propertyName)) {
684 				targetPropertyMap.put(propertyName, null);
685 			}
686 		}
687 	}
688 
689 	/**
690 	 * buildCommandLineArguments parses the commandline config parameter into a set of arguments
691 	 * @return an array of String containing the commandline arguments
692 	 */
buildCommandLineArguments()693 	protected String[] buildCommandLineArguments() {
694 		List<String> args = new ArrayList<>();
695 
696 		String commandLine = config.getInitParameter(CONFIG_COMMANDLINE);
697 		if (commandLine != null) {
698 			StringTokenizer tokenizer = new StringTokenizer(commandLine, WS_DELIM);
699 			while (tokenizer.hasMoreTokens()) {
700 				String arg = tokenizer.nextToken();
701 				if (arg.startsWith("\"")) { //$NON-NLS-1$
702 					if (arg.endsWith("\"")) { //$NON-NLS-1$
703 						if (arg.length() >= 2) {
704 							// strip the beginning and ending quotes
705 							arg = arg.substring(1, arg.length() - 1);
706 						}
707 					} else {
708 						String remainingArg = tokenizer.nextToken("\""); //$NON-NLS-1$
709 						arg = arg.substring(1) + remainingArg;
710 						// skip to next whitespace separated token
711 						tokenizer.nextToken(WS_DELIM);
712 					}
713 				} else if (arg.startsWith("'")) { //$NON-NLS-1$
714 					if (arg.endsWith("'")) { //$NON-NLS-1$
715 						if (arg.length() >= 2) {
716 							// strip the beginning and ending quotes
717 							arg = arg.substring(1, arg.length() - 1);
718 						}
719 					} else {
720 						String remainingArg = tokenizer.nextToken("'"); //$NON-NLS-1$
721 						arg = arg.substring(1) + remainingArg;
722 						// skip to next whitespace separated token
723 						tokenizer.nextToken(WS_DELIM);
724 					}
725 				} else if (arg.startsWith("-D")) { //$NON-NLS-1$
726 					int matchIndex = arg.indexOf("=\""); //$NON-NLS-1$
727 					if (matchIndex != -1) {
728 						if (!arg.substring(matchIndex + 2).endsWith("\"") && tokenizer.hasMoreTokens()) { //$NON-NLS-1$
729 							arg += tokenizer.nextToken("\"") + "\""; //$NON-NLS-1$ //$NON-NLS-2$
730 							// skip to next whitespace separated token
731 							tokenizer.nextToken(WS_DELIM);
732 						}
733 					}
734 				}
735 				args.add(arg);
736 			}
737 		}
738 		return args.toArray(new String[] {});
739 	}
740 
741 	/**
742 	 * stop is used to "shutdown" the framework and make it avialable for garbage collection.
743 	 * The default implementation also has special handling for Apache Commons Logging to "release" any
744 	 * resources associated with the frameworkContextClassLoader.
745 	 */
stop()746 	public synchronized void stop() {
747 		if (platformDirectory == null) {
748 			context.log("Shutdown unnecessary. (not deployed)"); //$NON-NLS-1$
749 			return;
750 		}
751 
752 		if (frameworkClassLoader == null) {
753 			context.log("Framework is already shutdown"); //$NON-NLS-1$
754 			return;
755 		}
756 
757 		ClassLoader original = Thread.currentThread().getContextClassLoader();
758 		try {
759 			Class<?> clazz = frameworkClassLoader.loadClass(STARTER);
760 			Method method = clazz.getDeclaredMethod("shutdown"); //$NON-NLS-1$
761 			Thread.currentThread().setContextClassLoader(frameworkContextClassLoader);
762 			method.invoke(clazz);
763 
764 			// ACL keys its loggers off of the ContextClassLoader which prevents GC without calling release.
765 			// This section explicitly calls release if ACL is used.
766 			try {
767 				clazz = this.getClass().getClassLoader().loadClass("org.apache.commons.logging.LogFactory"); //$NON-NLS-1$
768 				method = clazz.getDeclaredMethod("release", ClassLoader.class); //$NON-NLS-1$
769 				method.invoke(clazz, frameworkContextClassLoader);
770 			} catch (ClassNotFoundException e) {
771 				// ignore, ACL is not being used
772 			}
773 
774 		} catch (Exception e) {
775 			context.log("Error while stopping Framework", e); //$NON-NLS-1$
776 			return;
777 		} finally {
778 			frameworkClassLoader.close();
779 			frameworkClassLoader = null;
780 			frameworkContextClassLoader = null;
781 			Thread.currentThread().setContextClassLoader(original);
782 		}
783 	}
784 
785 	/**
786 	 * copyResource is a convenience method to recursively copy resources from the ServletContext to
787 	 * an installation target. The default behavior will create a directory if the resourcepath ends
788 	 * in '/' and a file otherwise.
789 	 * @param resourcePath - The resource root path
790 	 * @param target - The root location where resources are to be copied
791 	 */
copyResource(String resourcePath, File target)792 	protected void copyResource(String resourcePath, File target) {
793 		if (resourcePath.endsWith("/")) { //$NON-NLS-1$
794 			target.mkdir();
795 			Set<String> paths = context.getResourcePaths(resourcePath);
796 			if (paths == null)
797 				return;
798 			for (String path : paths) {
799 				File newFile = new File(target, path.substring(resourcePath.length()));
800 				copyResource(path, newFile);
801 			}
802 		} else {
803 			try {
804 				if (target.createNewFile()) {
805 					InputStream is = null;
806 					OutputStream os = null;
807 					try {
808 						is = context.getResourceAsStream(resourcePath);
809 						if (is == null)
810 							return;
811 						os = new FileOutputStream(target);
812 						byte[] buffer = new byte[8192];
813 						int bytesRead = is.read(buffer);
814 						while (bytesRead != -1) {
815 							os.write(buffer, 0, bytesRead);
816 							bytesRead = is.read(buffer);
817 						}
818 					} finally {
819 						if (is != null)
820 							is.close();
821 
822 						if (os != null)
823 							os.close();
824 					}
825 				}
826 			} catch (IOException e) {
827 				context.log("Error copying resources", e); //$NON-NLS-1$
828 			}
829 		}
830 	}
831 
832 	/**
833 	 * deleteDirectory is a convenience method to recursively delete a directory
834 	 * @param directory - the directory to delete.
835 	 * @return was the delete successful
836 	 */
deleteDirectory(File directory)837 	protected static boolean deleteDirectory(File directory) {
838 		if (directory.isDirectory()) {
839 			File[] files = directory.listFiles();
840 			for (File file : files) {
841 				if (file.isDirectory()) {
842 					deleteDirectory(file);
843 				} else {
844 					file.delete();
845 				}
846 			}
847 		}
848 		return directory.delete();
849 	}
850 
851 	/**
852 	 * Used when to set the ContextClassLoader when the BridgeServlet delegates to a Servlet
853 	 * inside the framework
854 	 * @return a Classloader with the OSGi framework's context class loader.
855 	 */
getFrameworkContextClassLoader()856 	public synchronized ClassLoader getFrameworkContextClassLoader() {
857 		return frameworkContextClassLoader;
858 	}
859 
860 	/**
861 	 * Platfom Directory is where the OSGi software is installed
862 	 * @return the framework install location
863 	 */
getPlatformDirectory()864 	protected synchronized File getPlatformDirectory() {
865 		return platformDirectory;
866 	}
867 
868 	/**
869 	 * loadProperties is a convenience method to load properties from a servlet context resource
870 	 * @param resource - The target to read properties from
871 	 * @return the properties
872 	 */
loadProperties(String resource)873 	protected Properties loadProperties(String resource) {
874 		Properties result = new Properties();
875 		InputStream in = null;
876 		try {
877 			URL location = context.getResource(resource);
878 			if (location != null) {
879 				in = location.openStream();
880 				result.load(in);
881 			}
882 		} catch (MalformedURLException e) {
883 			// no url to load from
884 		} catch (IOException e) {
885 			// its ok if there is no file
886 		} finally {
887 			if (in != null) {
888 				try {
889 					in.close();
890 				} catch (IOException e) {
891 					// ignore
892 				}
893 			}
894 		}
895 		return result;
896 	}
897 
898 	/***************************************************************************
899 	 * See org.eclipse.core.launcher [copy of searchFor, findMax,
900 	 * compareVersion, getVersionElements] TODO: If these methods were made
901 	 * public and static we could use them directly
902 	 **************************************************************************/
903 
904 	/**
905 	 * Searches for the given target directory starting in the "plugins" subdirectory
906 	 * of the given location.  If one is found then this location is returned;
907 	 * otherwise an exception is thrown.
908 	 * @param target
909 	 *
910 	 * @return the location where target directory was found
911 	 * @param start the location to begin searching
912 	 */
searchFor(final String target, String start)913 	protected String searchFor(final String target, String start) {
914 		FileFilter filter = new FileFilter() {
915 			@Override
916 			public boolean accept(File candidate) {
917 				return candidate.getName().equals(target) || candidate.getName().startsWith(target + "_"); //$NON-NLS-1$
918 			}
919 		};
920 		File[] candidates = new File(start).listFiles(filter);
921 		if (candidates == null)
922 			return null;
923 		String[] arrays = new String[candidates.length];
924 		for (int i = 0; i < arrays.length; i++) {
925 			arrays[i] = candidates[i].getName();
926 		}
927 		int result = findMax(arrays);
928 		if (result == -1)
929 			return null;
930 		return candidates[result].getAbsolutePath().replace(File.separatorChar, '/') + (candidates[result].isDirectory() ? "/" : ""); //$NON-NLS-1$//$NON-NLS-2$
931 	}
932 
findMax(String[] candidates)933 	protected int findMax(String[] candidates) {
934 		int result = -1;
935 		Object maxVersion = null;
936 		for (int i = 0; i < candidates.length; i++) {
937 			String name = candidates[i];
938 			String version = ""; //$NON-NLS-1$ // Note: directory with version suffix is always > than directory without version suffix
939 			int index = name.indexOf('_');
940 			if (index != -1)
941 				version = name.substring(index + 1);
942 			Object currentVersion = getVersionElements(version);
943 			if (maxVersion == null) {
944 				result = i;
945 				maxVersion = currentVersion;
946 			} else {
947 				if (compareVersion((Object[]) maxVersion, (Object[]) currentVersion) < 0) {
948 					result = i;
949 					maxVersion = currentVersion;
950 				}
951 			}
952 		}
953 		return result;
954 	}
955 
956 	/**
957 	 * Compares version strings.
958 	 * @param left
959 	 * @param right
960 	 * @return result of comparison, as integer;
961 	 * <code><0</code> if left < right;
962 	 * <code>0</code> if left == right;
963 	 * <code>>0</code> if left > right;
964 	 */
compareVersion(Object[] left, Object[] right)965 	private int compareVersion(Object[] left, Object[] right) {
966 
967 		int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major
968 		if (result != 0)
969 			return result;
970 
971 		result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor
972 		if (result != 0)
973 			return result;
974 
975 		result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service
976 		if (result != 0)
977 			return result;
978 
979 		return ((String) left[3]).compareTo((String) right[3]); // compare qualifier
980 	}
981 
982 	/**
983 	 * Do a quick parse of version identifier so its elements can be correctly compared.
984 	 * If we are unable to parse the full version, remaining elements are initialized
985 	 * with suitable defaults.
986 	 * @param version
987 	 * @return an array of size 4; first three elements are of type Integer (representing
988 	 * major, minor and service) and the fourth element is of type String (representing
989 	 * qualifier). Note, that returning anything else will cause exceptions in the caller.
990 	 */
getVersionElements(String version)991 	private Object[] getVersionElements(String version) {
992 		if (version.endsWith(DOT_JAR))
993 			version = version.substring(0, version.length() - 4);
994 		Object[] result = {Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0), ""}; //$NON-NLS-1$
995 		StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$
996 		String token;
997 		int i = 0;
998 		while (t.hasMoreTokens() && i < 4) {
999 			token = t.nextToken();
1000 			if (i < 3) {
1001 				// major, minor or service ... numeric values
1002 				try {
1003 					result[i++] = Integer.valueOf(token);
1004 				} catch (Exception e) {
1005 					// invalid number format - use default numbers (0) for the rest
1006 					break;
1007 				}
1008 			} else {
1009 				// qualifier ... string value
1010 				result[i++] = token;
1011 			}
1012 		}
1013 		return result;
1014 	}
1015 
1016 	/**
1017 	 * The ChildFirstURLClassLoader alters regular ClassLoader delegation and will check the URLs
1018 	 * used in its initialization for matching classes before delegating to it's parent.
1019 	 * Sometimes also referred to as a ParentLastClassLoader
1020 	 */
1021 	protected static class ChildFirstURLClassLoader extends CloseableURLClassLoader {
1022 		private static final boolean CHILDFIRST_REGISTERED_AS_PARALLEL;
1023 
1024 		static {
1025 			boolean registeredAsParallel;
1026 			try {
1027 				Method parallelCapableMetod = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable", (Class[]) null); //$NON-NLS-1$
1028 				parallelCapableMetod.setAccessible(true);
1029 				registeredAsParallel = ((Boolean) parallelCapableMetod.invoke(null, (Object[]) null)).booleanValue();
1030 			} catch (Throwable e) {
1031 				// must do everything to avoid failing in clinit
1032 				registeredAsParallel = false;
1033 			}
1034 			CHILDFIRST_REGISTERED_AS_PARALLEL = registeredAsParallel;
1035 		}
1036 
ChildFirstURLClassLoader(URL[] urls, ClassLoader parent)1037 		public ChildFirstURLClassLoader(URL[] urls, ClassLoader parent) {
1038 			super(urls, parent, false);
1039 		}
1040 
1041 		@Override
getResource(String name)1042 		public URL getResource(String name) {
1043 			URL resource = findResource(name);
1044 			if (resource == null) {
1045 				ClassLoader parent = getParent();
1046 				if (parent != null)
1047 					resource = parent.getResource(name);
1048 			}
1049 			return resource;
1050 		}
1051 
1052 		@Override
loadClass(String name, boolean resolve)1053 		protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
1054 			if (isRegisteredAsParallel()) {
1055 				return loadClass0(name, resolve);
1056 			}
1057 			synchronized (this) {
1058 				return loadClass0(name, resolve);
1059 			}
1060 		}
1061 
loadClass0(String name, boolean resolve)1062 		private Class<?> loadClass0(String name, boolean resolve) throws ClassNotFoundException {
1063 			Class<?> clazz = findLoadedClass(name);
1064 			if (clazz == null) {
1065 				try {
1066 					clazz = findClass(name);
1067 				} catch (ClassNotFoundException e) {
1068 					ClassLoader parent = getParent();
1069 					if (parent != null)
1070 						clazz = parent.loadClass(name);
1071 					else
1072 						clazz = getSystemClassLoader().loadClass(name);
1073 				}
1074 			}
1075 
1076 			if (resolve)
1077 				resolveClass(clazz);
1078 
1079 			return clazz;
1080 		}
1081 
1082 		// we want to ensure that the framework has AllPermissions
1083 		@Override
getPermissions(CodeSource codesource)1084 		protected PermissionCollection getPermissions(CodeSource codesource) {
1085 			return allPermissions;
1086 		}
1087 
1088 		@Override
isRegisteredAsParallel()1089 		protected boolean isRegisteredAsParallel() {
1090 			return CHILDFIRST_REGISTERED_AS_PARALLEL;
1091 		}
1092 
1093 	}
1094 
1095 }
1096