1 /******************************************************************************* 2 * Copyright (c) 2003, 2016 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 * IBM Corporation - initial API and implementation 13 * Alex Blewitt (bug 172969) 14 *******************************************************************************/ 15 package org.eclipse.core.runtime.adaptor; 16 17 import java.io.*; 18 import java.lang.reflect.Method; 19 import java.net.*; 20 import java.security.CodeSource; 21 import java.security.ProtectionDomain; 22 import java.util.*; 23 import java.util.concurrent.Semaphore; 24 import java.util.concurrent.TimeUnit; 25 import org.eclipse.core.runtime.internal.adaptor.*; 26 import org.eclipse.osgi.container.Module; 27 import org.eclipse.osgi.container.ModuleRevision; 28 import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace; 29 import org.eclipse.osgi.framework.log.FrameworkLog; 30 import org.eclipse.osgi.framework.log.FrameworkLogEntry; 31 import org.eclipse.osgi.framework.util.FilePath; 32 import org.eclipse.osgi.internal.debug.Debug; 33 import org.eclipse.osgi.internal.framework.EquinoxConfiguration; 34 import org.eclipse.osgi.internal.framework.EquinoxContainer; 35 import org.eclipse.osgi.internal.location.EquinoxLocations; 36 import org.eclipse.osgi.internal.location.LocationHelper; 37 import org.eclipse.osgi.internal.messages.Msg; 38 import org.eclipse.osgi.launch.Equinox; 39 import org.eclipse.osgi.report.resolution.ResolutionReport; 40 import org.eclipse.osgi.service.datalocation.Location; 41 import org.eclipse.osgi.service.environment.EnvironmentInfo; 42 import org.eclipse.osgi.service.runnable.ApplicationLauncher; 43 import org.eclipse.osgi.service.runnable.StartupMonitor; 44 import org.eclipse.osgi.storage.url.reference.Handler; 45 import org.eclipse.osgi.util.ManifestElement; 46 import org.eclipse.osgi.util.NLS; 47 import org.osgi.framework.*; 48 import org.osgi.framework.launch.Framework; 49 import org.osgi.framework.startlevel.BundleStartLevel; 50 import org.osgi.framework.startlevel.FrameworkStartLevel; 51 import org.osgi.framework.wiring.FrameworkWiring; 52 import org.osgi.resource.Resource; 53 import org.osgi.util.tracker.ServiceTracker; 54 55 /** 56 * Special startup class for the Eclipse Platform. This class cannot be 57 * instantiated; all functionality is provided by static methods. 58 * <p> 59 * The Eclipse Platform makes heavy use of Java class loaders for loading 60 * plug-ins. Even the Eclipse Runtime itself and the OSGi framework need 61 * to be loaded by special class loaders. The upshot is that a 62 * client program (such as a Java main program, a servlet) cannot 63 * reference any part of Eclipse directly. Instead, a client must use this 64 * loader class to start the platform, invoking functionality defined 65 * in plug-ins, and shutting down the platform when done. 66 * </p> 67 * <p>Note that the fields on this class are not API. </p> 68 * @since 3.0 69 * @noextend This class is not intended to be subclassed by clients. 70 */ 71 public class EclipseStarter { 72 private static BundleContext context; 73 private static boolean initialize = false; 74 public static boolean debug = false; 75 private static boolean running = false; 76 private static ServiceRegistration<?> defaultMonitorRegistration = null; 77 private static ServiceRegistration<?> appLauncherRegistration = null; 78 private static ServiceRegistration<?> splashStreamRegistration = null; 79 80 // command line arguments 81 private static final String CLEAN = "-clean"; //$NON-NLS-1$ 82 private static final String CONSOLE = "-console"; //$NON-NLS-1$ 83 private static final String CONSOLE_LOG = "-consoleLog"; //$NON-NLS-1$ 84 private static final String DEBUG = "-debug"; //$NON-NLS-1$ 85 private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$ 86 private static final String DEV = "-dev"; //$NON-NLS-1$ 87 private static final String WS = "-ws"; //$NON-NLS-1$ 88 private static final String OS = "-os"; //$NON-NLS-1$ 89 private static final String ARCH = "-arch"; //$NON-NLS-1$ 90 private static final String NL = "-nl"; //$NON-NLS-1$ 91 private static final String NL_EXTENSIONS = "-nlExtensions"; //$NON-NLS-1$ 92 private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$ 93 private static final String USER = "-user"; //$NON-NLS-1$ 94 private static final String NOEXIT = "-noExit"; //$NON-NLS-1$ 95 private static final String LAUNCHER = "-launcher"; //$NON-NLS-1$ 96 97 // this is more of an Eclipse argument but this OSGi implementation stores its 98 // metadata alongside Eclipse's. 99 private static final String DATA = "-data"; //$NON-NLS-1$ 100 101 // System properties 102 public static final String PROP_BUNDLES = "osgi.bundles"; //$NON-NLS-1$ 103 public static final String PROP_BUNDLES_STARTLEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$ //The start level used to install the bundles 104 public static final String PROP_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$ 105 public static final String PROP_INITIAL_STARTLEVEL = "osgi.startLevel"; //$NON-NLS-1$ //The start level when the fwl start 106 public static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$ 107 public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$ 108 public static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$ 109 public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$ 110 public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass"; //$NON-NLS-1$ 111 public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$ 112 public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$ 113 public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$ 114 public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$ 115 private static final String PROP_NL_EXTENSIONS = "osgi.nl.extensions"; //$NON-NLS-1$ 116 public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$ 117 public static final String PROP_ADAPTOR = "osgi.adaptor"; //$NON-NLS-1$ 118 public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$ 119 public static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$ 120 public static final String PROP_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$ 121 public static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$ 122 public static final String PROP_FRAMEWORK_SHAPE = "osgi.framework.shape"; //$NON-NLS-1$ //the shape of the fwk (jar, or folder) 123 public static final String PROP_NOSHUTDOWN = "osgi.noShutdown"; //$NON-NLS-1$ 124 125 public static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$ 126 public static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$ 127 public static final String PROP_CONSOLE_LOG = "eclipse.consoleLog"; //$NON-NLS-1$ 128 public static final String PROP_IGNOREAPP = "eclipse.ignoreApp"; //$NON-NLS-1$ 129 public static final String PROP_REFRESH_BUNDLES = "eclipse.refreshBundles"; //$NON-NLS-1$ 130 private static final String PROP_ALLOW_APPRELAUNCH = "eclipse.allowAppRelaunch"; //$NON-NLS-1$ 131 private static final String PROP_APPLICATION_LAUNCHDEFAULT = "eclipse.application.launchDefault"; //$NON-NLS-1$ 132 133 private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$ 134 private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$ 135 private static final String REFERENCE_PROTOCOL = "reference"; //$NON-NLS-1$ 136 private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$ 137 138 private static final int DEFAULT_INITIAL_STARTLEVEL = 6; // default value for legacy purposes 139 private static final String DEFAULT_BUNDLES_STARTLEVEL = "4"; //$NON-NLS-1$ 140 141 private static FrameworkLog log; 142 // directory of serch candidates keyed by directory abs path -> directory listing (bug 122024) 143 private static Map<String, String[]> searchCandidates = new HashMap<>(4); 144 private static EclipseAppLauncher appLauncher; 145 private static List<Runnable> shutdownHandlers; 146 147 private static ConsoleManager consoleMgr = null; 148 149 private static Map<String, String> configuration = null; 150 private static Framework framework = null; 151 private static EquinoxConfiguration equinoxConfig; 152 private static String[] allArgs = null; 153 private static String[] frameworkArgs = null; 154 private static String[] appArgs = null; 155 getProperty(String key)156 private synchronized static String getProperty(String key) { 157 if (equinoxConfig != null) { 158 return equinoxConfig.getConfiguration(key); 159 } 160 return getConfiguration().get(key); 161 } 162 getProperty(String key, String dft)163 private synchronized static String getProperty(String key, String dft) { 164 if (equinoxConfig != null) { 165 return equinoxConfig.getConfiguration(key, dft); 166 } 167 String result = getConfiguration().get(key); 168 return result == null ? dft : result; 169 } 170 setProperty(String key, String value)171 private synchronized static Object setProperty(String key, String value) { 172 if (equinoxConfig != null) { 173 return equinoxConfig.setProperty(key, value); 174 } 175 if ("true".equals(getConfiguration().get(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES))) { //$NON-NLS-1$ 176 System.setProperty(key, value); 177 } 178 return getConfiguration().put(key, value); 179 } 180 clearProperty(String key)181 private synchronized static Object clearProperty(String key) { 182 if (equinoxConfig != null) { 183 return equinoxConfig.clearConfiguration(key); 184 } 185 return getConfiguration().remove(key); 186 } 187 getConfiguration()188 private synchronized static Map<String, String> getConfiguration() { 189 if (configuration == null) { 190 configuration = new HashMap<>(); 191 // TODO hack to set these to defaults for EclipseStarter 192 // Note that this hack does not allow this property to be specified in config.ini 193 configuration.put(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES, System.getProperty(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES, "true")); //$NON-NLS-1$ 194 // we handle this compatibility setting special for EclipseStarter 195 String systemCompatibilityBoot = System.getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION); 196 if (systemCompatibilityBoot != null) { 197 // The system properties have a specific setting; use it 198 configuration.put(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION, systemCompatibilityBoot); 199 } else { 200 // set a default value; but this value can be overriden by the config.ini 201 configuration.put(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION + EquinoxConfiguration.PROP_DEFAULT_SUFFIX, "true"); //$NON-NLS-1$ 202 } 203 204 String dsDelayedKeepInstances = System.getProperty(EquinoxConfiguration.PROP_DS_DELAYED_KEEPINSTANCES); 205 if (dsDelayedKeepInstances != null) { 206 // The system properties have a specific setting; use it 207 configuration.put(EquinoxConfiguration.PROP_DS_DELAYED_KEEPINSTANCES, dsDelayedKeepInstances); 208 } else { 209 // set a default value; but this value can be overriden by the config.ini 210 configuration.put(EquinoxConfiguration.PROP_DS_DELAYED_KEEPINSTANCES + EquinoxConfiguration.PROP_DEFAULT_SUFFIX, "true"); //$NON-NLS-1$ 211 } 212 } 213 return configuration; 214 } 215 216 /** 217 * This is the main to start osgi. 218 * It only works when the framework is being jared as a single jar 219 */ main(String[] args)220 public static void main(String[] args) throws Exception { 221 if (getProperty("eclipse.startTime") == null) //$NON-NLS-1$ 222 setProperty("eclipse.startTime", Long.toString(System.currentTimeMillis())); //$NON-NLS-1$ 223 if (getProperty(PROP_NOSHUTDOWN) == null) 224 setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$ 225 // set the compatibility boot delegation flag to false to get "standard" OSGi behavior WRT boot delegation (bug 178477) 226 if (getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION) == null) 227 setProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION, "false"); //$NON-NLS-1$ 228 Object result = run(args, null); 229 if (result instanceof Integer && !Boolean.valueOf(getProperty(PROP_NOSHUTDOWN)).booleanValue()) 230 System.exit(((Integer) result).intValue()); 231 } 232 233 /** 234 * Launches the platform and runs a single application. The application is either identified 235 * in the given arguments (e.g., -application <app id>) or in the <code>eclipse.application</code> 236 * System property. This convenience method starts 237 * up the platform, runs the indicated application, and then shuts down the 238 * platform. The platform must not be running already. 239 * 240 * @param args the command line-style arguments used to configure the platform 241 * @param endSplashHandler the block of code to run to tear down the splash 242 * screen or <code>null</code> if no tear down is required 243 * @return the result of running the application 244 * @throws Exception if anything goes wrong 245 */ run(String[] args, Runnable endSplashHandler)246 public static Object run(String[] args, Runnable endSplashHandler) throws Exception { 247 if (running) 248 throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING); 249 boolean startupFailed = true; 250 try { 251 startup(args, endSplashHandler); 252 startupFailed = false; 253 if (Boolean.valueOf(getProperty(PROP_IGNOREAPP)).booleanValue() || isForcedRestart()) 254 return null; 255 return run(null); 256 } catch (Throwable e) { 257 // ensure the splash screen is down 258 if (endSplashHandler != null) 259 endSplashHandler.run(); 260 // may use startupFailed to understand where the error happened 261 FrameworkLogEntry logEntry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, startupFailed ? Msg.ECLIPSE_STARTUP_STARTUP_ERROR : Msg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null); 262 if (log != null) 263 log.log(logEntry); 264 else 265 // TODO desperate measure - ideally, we should write this to disk (a la Main.log) 266 e.printStackTrace(); 267 } finally { 268 try { 269 // The application typically sets the exit code however the framework can request that 270 // it be re-started. We need to check for this and potentially override the exit code. 271 if (isForcedRestart()) 272 setProperty(PROP_EXITCODE, "23"); //$NON-NLS-1$ 273 if (!Boolean.valueOf(getProperty(PROP_NOSHUTDOWN)).booleanValue()) 274 shutdown(); 275 } catch (Throwable e) { 276 FrameworkLogEntry logEntry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, Msg.ECLIPSE_STARTUP_SHUTDOWN_ERROR, 1, e, null); 277 if (log != null) 278 log.log(logEntry); 279 else 280 // TODO desperate measure - ideally, we should write this to disk (a la Main.log) 281 e.printStackTrace(); 282 } 283 } 284 // we only get here if an error happened 285 if (getProperty(PROP_EXITCODE) == null) { 286 setProperty(PROP_EXITCODE, "13"); //$NON-NLS-1$ 287 setProperty(PROP_EXITDATA, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_CHECK_LOG, log == null ? null : log.getFile().getPath())); 288 } 289 return null; 290 } 291 292 /** 293 * Returns true if the platform is already running, false otherwise. 294 * @return whether or not the platform is already running 295 */ isRunning()296 public static boolean isRunning() { 297 return running; 298 } 299 300 /** 301 * Starts the platform and sets it up to run a single application. The application is either identified 302 * in the given arguments (e.g., -application <app id>) or in the <code>eclipse.application</code> 303 * System property. The platform must not be running already. 304 * <p> 305 * The given runnable (if not <code>null</code>) is used to tear down the splash screen if required. 306 * </p> 307 * @param args the arguments passed to the application 308 * @return BundleContext the context of the system bundle 309 * @throws Exception if anything goes wrong 310 */ startup(String[] args, Runnable endSplashHandler)311 public static BundleContext startup(String[] args, Runnable endSplashHandler) throws Exception { 312 if (running) 313 throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING); 314 processCommandLine(args); 315 framework = new Equinox(getConfiguration()); 316 framework.init(); 317 context = framework.getBundleContext(); 318 ServiceReference<FrameworkLog> logRef = context.getServiceReference(FrameworkLog.class); 319 log = context.getService(logRef); 320 ServiceReference<EnvironmentInfo> configRef = context.getServiceReference(EnvironmentInfo.class); 321 equinoxConfig = (EquinoxConfiguration) context.getService(configRef); 322 323 equinoxConfig.setAllArgs(allArgs); 324 equinoxConfig.setFrameworkArgs(frameworkArgs); 325 equinoxConfig.setAppArgs(appArgs); 326 327 registerFrameworkShutdownHandlers(); 328 publishSplashScreen(endSplashHandler); 329 consoleMgr = ConsoleManager.startConsole(context, equinoxConfig); 330 331 Bundle[] startBundles = loadBasicBundles(); 332 333 if (startBundles == null || ("true".equals(getProperty(PROP_REFRESH_BUNDLES)) && refreshPackages(getCurrentBundles(false)))) { //$NON-NLS-1$ 334 waitForShutdown(); 335 return context; // cannot continue; loadBasicBundles caused refreshPackages to shutdown the framework 336 } 337 338 framework.start(); 339 340 if (isForcedRestart()) { 341 return context; 342 } 343 // set the framework start level to the ultimate value. This will actually start things 344 // running if they are persistently active. 345 setStartLevel(getStartLevel()); 346 // they should all be active by this time 347 ensureBundlesActive(startBundles); 348 349 // in the case where the built-in console is disabled we should try to start the console bundle 350 try { 351 consoleMgr.checkForConsoleBundle(); 352 } catch (BundleException e) { 353 FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null); 354 log.log(entry); 355 } 356 // TODO should log unresolved bundles if in debug or dev mode 357 running = true; 358 return context; 359 } 360 getStartLevel()361 private static int getStartLevel() { 362 String level = getProperty(PROP_INITIAL_STARTLEVEL); 363 if (level != null) 364 try { 365 return Integer.parseInt(level); 366 } catch (NumberFormatException e) { 367 if (debug) 368 Debug.println("Start level = " + level + " parsed. Using hardcoded default: 6"); //$NON-NLS-1$ //$NON-NLS-2$ 369 } 370 return DEFAULT_INITIAL_STARTLEVEL; 371 } 372 373 /** 374 * Runs the application for which the platform was started. The platform 375 * must be running. 376 * <p> 377 * The given argument is passed to the application being run. If it is <code>null</code> 378 * then the command line arguments used in starting the platform, and not consumed 379 * by the platform code, are passed to the application as a <code>String[]</code>. 380 * </p> 381 * @param argument the argument passed to the application. May be <code>null</code> 382 * @return the result of running the application 383 * @throws Exception if anything goes wrong 384 */ run(Object argument)385 public static Object run(Object argument) throws Exception { 386 if (!running) 387 throw new IllegalStateException(Msg.ECLIPSE_STARTUP_NOT_RUNNING); 388 // if we are just initializing, do not run the application just return. 389 if (initialize) 390 return Integer.valueOf(0); 391 try { 392 if (appLauncher == null) { 393 394 boolean launchDefault = Boolean.parseBoolean(getProperty(PROP_APPLICATION_LAUNCHDEFAULT, "true")); //$NON-NLS-1$ 395 // create the ApplicationLauncher and register it as a service 396 appLauncher = new EclipseAppLauncher(context, Boolean.parseBoolean(getProperty(PROP_ALLOW_APPRELAUNCH)), launchDefault, log, equinoxConfig); 397 appLauncherRegistration = context.registerService(ApplicationLauncher.class.getName(), appLauncher, null); 398 // must start the launcher AFTER service restration because this method 399 // blocks and runs the application on the current thread. This method 400 // will return only after the application has stopped. 401 return appLauncher.start(argument); 402 } 403 return appLauncher.reStart(argument); 404 } catch (Exception e) { 405 if (log != null && context != null) { // context can be null if OSGi failed to launch (bug 151413) 406 ResolutionReport report = context.getBundle().adapt(Module.class).getContainer().resolve(null, false); 407 for (Resource unresolved : report.getEntries().keySet()) { 408 String bsn = ((ModuleRevision) unresolved).getSymbolicName(); 409 FrameworkLogEntry logEntry = new FrameworkLogEntry(bsn != null ? bsn : EquinoxContainer.NAME, FrameworkLogEntry.WARNING, 0, Msg.Module_ResolveError + report.getResolutionReportMessage(unresolved), 1, null, null); 410 log.log(logEntry); 411 } 412 } 413 throw e; 414 } 415 } 416 417 /** 418 * Shuts down the Platform. The state of the Platform is not automatically 419 * saved before shutting down. 420 * <p> 421 * On return, the Platform will no longer be running (but could be re-launched 422 * with another call to startup). If relaunching, care must be taken to reinitialize 423 * any System properties which the platform uses (e.g., osgi.instance.area) as 424 * some policies in the platform do not allow resetting of such properties on 425 * subsequent runs. 426 * </p><p> 427 * Any objects handed out by running Platform, 428 * including Platform runnables obtained via getRunnable, will be 429 * permanently invalid. The effects of attempting to invoke methods 430 * on invalid objects is undefined. 431 * </p> 432 * @throws Exception if anything goes wrong 433 */ shutdown()434 public static void shutdown() throws Exception { 435 if (!running || framework == null) 436 return; 437 if (framework.getState() == Bundle.ACTIVE) { 438 if (appLauncherRegistration != null) 439 appLauncherRegistration.unregister(); 440 if (splashStreamRegistration != null) 441 splashStreamRegistration.unregister(); 442 if (defaultMonitorRegistration != null) 443 defaultMonitorRegistration.unregister(); 444 } 445 if (appLauncher != null) 446 appLauncher.shutdown(); 447 appLauncherRegistration = null; 448 appLauncher = null; 449 splashStreamRegistration = null; 450 defaultMonitorRegistration = null; 451 if (consoleMgr != null) { 452 consoleMgr.stopConsole(); 453 consoleMgr = null; 454 } 455 if (framework.getState() == Bundle.ACTIVE) { 456 framework.stop(); 457 framework.waitForStop(0); 458 framework = null; 459 } 460 configuration = null; 461 equinoxConfig = null; 462 context = null; 463 running = false; 464 } 465 ensureBundlesActive(Bundle[] bundles)466 private static void ensureBundlesActive(Bundle[] bundles) { 467 for (Bundle bundle : bundles) { 468 if (bundle.getState() != Bundle.ACTIVE) { 469 if (bundle.getState() == Bundle.INSTALLED) { 470 // Log that the bundle is not resolved 471 log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundle.getLocation()), 0, null, null)); 472 continue; 473 } 474 // check that the startlevel allows the bundle to be active (111550) 475 FrameworkStartLevel fwStartLevel = context.getBundle().adapt(FrameworkStartLevel.class); 476 BundleStartLevel bundleStartLevel = bundle.adapt(BundleStartLevel.class); 477 if (fwStartLevel != null && (bundleStartLevel.getStartLevel() <= fwStartLevel.getStartLevel())) { 478 log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_ACTIVE, bundle), 0, null, null)); 479 } 480 } 481 } 482 } 483 publishSplashScreen(final Runnable endSplashHandler)484 private static void publishSplashScreen(final Runnable endSplashHandler) { 485 if (endSplashHandler == null) 486 return; 487 // register the output stream to the launcher if it exists 488 try { 489 Method method = endSplashHandler.getClass().getMethod("getOutputStream", new Class[0]); //$NON-NLS-1$ 490 Object outputStream = method.invoke(endSplashHandler, new Object[0]); 491 if (outputStream instanceof OutputStream) { 492 Dictionary<String, Object> osProperties = new Hashtable<>(); 493 osProperties.put("name", "splashstream"); //$NON-NLS-1$//$NON-NLS-2$ 494 splashStreamRegistration = context.registerService(OutputStream.class.getName(), outputStream, osProperties); 495 } 496 } catch (Exception ex) { 497 // ignore 498 } 499 // keep this splash handler as the default startup monitor 500 try { 501 Dictionary<String, Object> monitorProps = new Hashtable<>(); 502 monitorProps.put(Constants.SERVICE_RANKING, Integer.valueOf(Integer.MIN_VALUE)); 503 defaultMonitorRegistration = context.registerService(StartupMonitor.class.getName(), new DefaultStartupMonitor(endSplashHandler, equinoxConfig), monitorProps); 504 } catch (IllegalStateException e) { 505 //splash handler did not provide the necessary methods, ignore it 506 } 507 } 508 509 @SuppressWarnings("deprecation") searchForBundle(String name, String parent)510 private static URL searchForBundle(String name, String parent) throws MalformedURLException { 511 URL url = null; 512 File fileLocation = null; 513 boolean reference = false; 514 try { 515 createURL(name); // quick check to see if the name is a valid URL 516 url = createURL(new File(parent).toURL(), name); 517 } catch (MalformedURLException e) { 518 // TODO this is legacy support for non-URL names. It should be removed eventually. 519 // if name was not a URL then construct one. 520 // Assume it should be a reference and that it is relative. This support need not 521 // be robust as it is temporary.. 522 File child = new File(name); 523 fileLocation = child.isAbsolute() ? child : new File(parent, name); 524 url = createURL(REFERENCE_PROTOCOL, null, fileLocation.toURL().toExternalForm()); 525 reference = true; 526 } 527 // if the name was a URL then see if it is relative. If so, insert syspath. 528 if (!reference) { 529 URL baseURL = url; 530 // if it is a reference URL then strip off the reference: and set base to the file:... 531 if (url.getProtocol().equals(REFERENCE_PROTOCOL)) { 532 reference = true; 533 String baseSpec = url.getPath(); 534 if (baseSpec.startsWith(FILE_SCHEME)) { 535 File child = new File(baseSpec.substring(5)); 536 baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL(); 537 } else 538 baseURL = createURL(baseSpec); 539 } 540 541 fileLocation = new File(baseURL.getPath()); 542 // if the location is relative, prefix it with the parent 543 if (!fileLocation.isAbsolute()) 544 fileLocation = new File(parent, fileLocation.toString()); 545 } 546 // If the result is a reference then search for the real result and 547 // reconstruct the answer. 548 if (reference) { 549 String result = searchFor(fileLocation.getName(), new File(fileLocation.getParent()).getAbsolutePath()); 550 if (result != null) 551 url = createURL(REFERENCE_PROTOCOL, null, FILE_SCHEME + result); 552 else 553 return null; 554 } 555 556 // finally we have something worth trying 557 try { 558 URLConnection result = LocationHelper.getConnection(url); 559 result.connect(); 560 return url; 561 } catch (IOException e) { 562 // int i = location.lastIndexOf('_'); 563 // return i == -1? location : location.substring(0, i); 564 return null; 565 } 566 } 567 568 /* 569 * Ensure all basic bundles are installed, resolved and scheduled to start. Returns an array containing 570 * all basic bundles that are marked to start. 571 * Returns null if the framework has been shutdown as a result of refreshPackages 572 */ loadBasicBundles()573 private static Bundle[] loadBasicBundles() throws InterruptedException { 574 long startTime = System.currentTimeMillis(); 575 String osgiBundles = getProperty(PROP_BUNDLES); 576 String osgiExtensions = getProperty(PROP_EXTENSIONS); 577 if (osgiExtensions != null && osgiExtensions.length() > 0) { 578 osgiBundles = osgiExtensions + ',' + osgiBundles; 579 setProperty(PROP_BUNDLES, osgiBundles); 580 } 581 String[] installEntries = getArrayFromList(osgiBundles, ","); //$NON-NLS-1$ 582 // get the initial bundle list from the installEntries 583 InitialBundle[] initialBundles = getInitialBundles(installEntries); 584 // get the list of currently installed initial bundles from the framework 585 Bundle[] curInitBundles = getCurrentBundles(true); 586 587 // list of bundles to be refreshed 588 List<Bundle> toRefresh = new ArrayList<>(curInitBundles.length); 589 // uninstall any of the currently installed bundles that do not exist in the 590 // initial bundle list from installEntries. 591 uninstallBundles(curInitBundles, initialBundles, toRefresh); 592 593 // install the initialBundles that are not already installed. 594 List<Bundle> startBundles = new ArrayList<>(installEntries.length); 595 List<Bundle> lazyActivationBundles = new ArrayList<>(installEntries.length); 596 installBundles(initialBundles, curInitBundles, startBundles, lazyActivationBundles, toRefresh); 597 598 // If we installed/uninstalled something, force a refresh of all installed/uninstalled bundles 599 if (!toRefresh.isEmpty() && refreshPackages(toRefresh.toArray(new Bundle[toRefresh.size()]))) 600 return null; // cannot continue; refreshPackages shutdown the framework 601 602 // schedule all basic bundles to be started 603 Bundle[] startInitBundles = startBundles.toArray(new Bundle[startBundles.size()]); 604 Bundle[] lazyInitBundles = lazyActivationBundles.toArray(new Bundle[lazyActivationBundles.size()]); 605 startBundles(startInitBundles, lazyInitBundles); 606 607 if (debug) 608 Debug.println("Time to load bundles: " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$ 609 return startInitBundles; 610 } 611 getInitialBundles(String[] installEntries)612 private static InitialBundle[] getInitialBundles(String[] installEntries) { 613 searchCandidates.clear(); 614 List<InitialBundle> result = new ArrayList<>(installEntries.length); 615 int defaultStartLevel = Integer.parseInt(getProperty(PROP_BUNDLES_STARTLEVEL, DEFAULT_BUNDLES_STARTLEVEL)); 616 String syspath = getSysPath(); 617 // should canonicalize the syspath. 618 try { 619 syspath = new File(syspath).getCanonicalPath(); 620 } catch (IOException ioe) { 621 // do nothing 622 } 623 Collection<ServiceReference<Location>> installLocRef; 624 try { 625 installLocRef = context.getServiceReferences(Location.class, Location.INSTALL_FILTER); 626 } catch (InvalidSyntaxException e) { 627 throw new RuntimeException(e); 628 } 629 Location installLocation = installLocRef == null ? null : context.getService(installLocRef.iterator().next()); 630 if (installLocation == null) { 631 throw new IllegalStateException(Msg.EclipseStarter_InstallLocation); 632 } 633 for (String name : installEntries) { 634 int level = defaultStartLevel; 635 boolean start = false; 636 int index = name.lastIndexOf('@'); 637 if (index >= 0) { 638 String[] attributes = getArrayFromList(name.substring(index + 1, name.length()), ":"); //$NON-NLS-1$ 639 for (String attribute : attributes) { 640 if (attribute.equals("start")) //$NON-NLS-1$ 641 start = true; 642 else { 643 try { 644 level = Integer.parseInt(attribute); 645 } catch (NumberFormatException e) { // bug 188089 646 index = name.length(); 647 continue; 648 } 649 } 650 } 651 name = name.substring(0, index); 652 } 653 try { 654 URL location = searchForBundle(name, syspath); 655 if (location == null) { 656 FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_BUNDLE_NOT_FOUND, name), 0, null, null); 657 log.log(entry); 658 // skip this entry 659 continue; 660 } 661 location = makeRelative(installLocation.getURL(), location); 662 String locationString = INITIAL_LOCATION + location.toExternalForm(); 663 result.add(new InitialBundle(locationString, location, level, start)); 664 }catch (IOException e) { 665 log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null)); 666 } 667 } 668 return result.toArray(new InitialBundle[result.size()]); 669 } 670 671 // returns true if the refreshPackages operation caused the framework to shutdown refreshPackages(Bundle[] bundles)672 private static boolean refreshPackages(Bundle[] bundles) throws InterruptedException { 673 FrameworkWiring frameworkWiring = context.getBundle().adapt(FrameworkWiring.class); 674 if (frameworkWiring == null) 675 return false; 676 Semaphore semaphore = new Semaphore(0); 677 StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.PACKAGES_REFRESHED); 678 context.addBundleListener(listener); 679 frameworkWiring.refreshBundles(Arrays.asList(bundles), listener); 680 updateSplash(semaphore, listener); 681 return isForcedRestart(); 682 } 683 waitForShutdown()684 private static void waitForShutdown() { 685 // wait for the system bundle to stop 686 try { 687 framework.waitForStop(0); 688 } catch (InterruptedException e) { 689 Thread.interrupted(); 690 throw new RuntimeException(e); 691 } 692 } 693 processCommandLine(String[] args)694 private static void processCommandLine(String[] args) throws Exception { 695 allArgs = args; 696 if (args.length == 0) { 697 frameworkArgs = args; 698 return; 699 } 700 int[] configArgs = new int[args.length]; 701 configArgs[0] = -1; // need to initialize the first element to something that could not be an index. 702 int configArgIndex = 0; 703 for (int i = 0; i < args.length; i++) { 704 boolean found = false; 705 // check for args without parameters (i.e., a flag arg) 706 707 // check if debug should be enabled for the entire platform 708 // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), 709 // simply enable debug. Otherwise, assume that that the following arg is 710 // actually the filename of an options file. This will be processed below. 711 if (args[i].equalsIgnoreCase(DEBUG) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ 712 setProperty(PROP_DEBUG, ""); //$NON-NLS-1$ 713 debug = true; 714 found = true; 715 } 716 717 // check if development mode should be enabled for the entire platform 718 // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), 719 // simply enable development mode. Otherwise, assume that that the following arg is 720 // actually some additional development time class path entries. This will be processed below. 721 if (args[i].equalsIgnoreCase(DEV) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ 722 setProperty(PROP_DEV, ""); //$NON-NLS-1$ 723 found = true; 724 } 725 726 // look for the initialization arg 727 if (args[i].equalsIgnoreCase(INITIALIZE)) { 728 initialize = true; 729 found = true; 730 } 731 732 // look for the clean flag. 733 if (args[i].equalsIgnoreCase(CLEAN)) { 734 setProperty(PROP_CLEAN, "true"); //$NON-NLS-1$ 735 found = true; 736 } 737 738 // look for the consoleLog flag 739 if (args[i].equalsIgnoreCase(CONSOLE_LOG)) { 740 setProperty(PROP_CONSOLE_LOG, "true"); //$NON-NLS-1$ 741 found = true; 742 } 743 744 // look for the console with no port. 745 if (args[i].equalsIgnoreCase(CONSOLE) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ 746 setProperty(PROP_CONSOLE, ""); //$NON-NLS-1$ 747 found = true; 748 } 749 750 if (args[i].equalsIgnoreCase(NOEXIT)) { 751 setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$ 752 found = true; 753 } 754 755 if (found) { 756 configArgs[configArgIndex++] = i; 757 continue; 758 } 759 // check for args with parameters. If we are at the last argument or if the next one 760 // has a '-' as the first character, then we can't have an arg with a parm so continue. 761 if (i == args.length - 1 || args[i + 1].startsWith("-")) { //$NON-NLS-1$ 762 continue; 763 } 764 String arg = args[++i]; 765 766 // look for the console and port. 767 if (args[i - 1].equalsIgnoreCase(CONSOLE)) { 768 setProperty(PROP_CONSOLE, arg); 769 found = true; 770 } 771 772 // look for the configuration location . 773 if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) { 774 setProperty(EquinoxLocations.PROP_CONFIG_AREA, arg); 775 found = true; 776 } 777 778 // look for the data location for this instance. 779 if (args[i - 1].equalsIgnoreCase(DATA)) { 780 setProperty(EquinoxLocations.PROP_INSTANCE_AREA, arg); 781 found = true; 782 } 783 784 // look for the user location for this instance. 785 if (args[i - 1].equalsIgnoreCase(USER)) { 786 setProperty(EquinoxLocations.PROP_USER_AREA, arg); 787 found = true; 788 } 789 790 // look for the launcher location 791 if (args[i - 1].equalsIgnoreCase(LAUNCHER)) { 792 setProperty(EquinoxLocations.PROP_LAUNCHER, arg); 793 found = true; 794 } 795 // look for the development mode and class path entries. 796 if (args[i - 1].equalsIgnoreCase(DEV)) { 797 setProperty(PROP_DEV, arg); 798 found = true; 799 } 800 801 // look for the debug mode and option file location. 802 if (args[i - 1].equalsIgnoreCase(DEBUG)) { 803 setProperty(PROP_DEBUG, arg); 804 debug = true; 805 found = true; 806 } 807 808 // look for the window system. 809 if (args[i - 1].equalsIgnoreCase(WS)) { 810 setProperty(PROP_WS, arg); 811 found = true; 812 } 813 814 // look for the operating system 815 if (args[i - 1].equalsIgnoreCase(OS)) { 816 setProperty(PROP_OS, arg); 817 found = true; 818 } 819 820 // look for the system architecture 821 if (args[i - 1].equalsIgnoreCase(ARCH)) { 822 setProperty(PROP_ARCH, arg); 823 found = true; 824 } 825 826 // look for the nationality/language 827 if (args[i - 1].equalsIgnoreCase(NL)) { 828 setProperty(PROP_NL, arg); 829 found = true; 830 } 831 832 // look for the locale extensions 833 if (args[i - 1].equalsIgnoreCase(NL_EXTENSIONS)) { 834 setProperty(PROP_NL_EXTENSIONS, arg); 835 found = true; 836 } 837 838 // done checking for args. Remember where an arg was found 839 if (found) { 840 configArgs[configArgIndex++] = i - 1; 841 configArgs[configArgIndex++] = i; 842 } 843 } 844 845 // remove all the arguments consumed by this argument parsing 846 if (configArgIndex == 0) { 847 frameworkArgs = new String[0]; 848 appArgs = args; 849 return; 850 } 851 appArgs = new String[args.length - configArgIndex]; 852 frameworkArgs = new String[configArgIndex]; 853 configArgIndex = 0; 854 int j = 0; 855 int k = 0; 856 for (int i = 0; i < args.length; i++) { 857 if (i == configArgs[configArgIndex]) { 858 frameworkArgs[k++] = args[i]; 859 configArgIndex++; 860 } else 861 appArgs[j++] = args[i]; 862 } 863 return; 864 } 865 866 /** 867 * Returns the result of converting a list of comma-separated tokens into an array 868 * 869 * @return the array of string tokens 870 * @param prop the initial comma-separated string 871 */ getArrayFromList(String prop, String separator)872 private static String[] getArrayFromList(String prop, String separator) { 873 return ManifestElement.getArrayFromList(prop, separator); 874 } 875 getSysPath()876 protected static String getSysPath() { 877 String result = getProperty(PROP_SYSPATH); 878 if (result != null) 879 return result; 880 result = getSysPathFromURL(getProperty(PROP_FRAMEWORK)); 881 if (result == null) 882 result = getSysPathFromCodeSource(); 883 if (result == null) 884 throw new IllegalStateException("Can not find the system path."); //$NON-NLS-1$ 885 if (Character.isUpperCase(result.charAt(0))) { 886 char[] chars = result.toCharArray(); 887 chars[0] = Character.toLowerCase(chars[0]); 888 result = new String(chars); 889 } 890 setProperty(PROP_SYSPATH, result); 891 return result; 892 } 893 getSysPathFromURL(String urlSpec)894 private static String getSysPathFromURL(String urlSpec) { 895 if (urlSpec == null) 896 return null; 897 URL url = LocationHelper.buildURL(urlSpec, false); 898 if (url == null) 899 return null; 900 File fwkFile = LocationHelper.decodePath(new File(url.getPath())); 901 fwkFile = new File(fwkFile.getAbsolutePath()); 902 fwkFile = new File(fwkFile.getParent()); 903 return fwkFile.getAbsolutePath(); 904 } 905 getSysPathFromCodeSource()906 private static String getSysPathFromCodeSource() { 907 ProtectionDomain pd = EclipseStarter.class.getProtectionDomain(); 908 if (pd == null) 909 return null; 910 CodeSource cs = pd.getCodeSource(); 911 if (cs == null) 912 return null; 913 URL url = cs.getLocation(); 914 if (url == null) 915 return null; 916 String result = url.getPath(); 917 if (File.separatorChar == '\\') { 918 // in case on windows the \ is used 919 result = result.replace('\\', '/'); 920 } 921 if (result.endsWith(".jar")) { //$NON-NLS-1$ 922 result = result.substring(0, result.lastIndexOf('/')); 923 if ("folder".equals(getProperty(PROP_FRAMEWORK_SHAPE))) //$NON-NLS-1$ 924 result = result.substring(0, result.lastIndexOf('/')); 925 } else { 926 if (result.endsWith("/")) //$NON-NLS-1$ 927 result = result.substring(0, result.length() - 1); 928 result = result.substring(0, result.lastIndexOf('/')); 929 result = result.substring(0, result.lastIndexOf('/')); 930 } 931 return result; 932 } 933 getCurrentBundles(boolean includeInitial)934 private static Bundle[] getCurrentBundles(boolean includeInitial) { 935 Bundle[] installed = context.getBundles(); 936 List<Bundle> initial = new ArrayList<>(); 937 for (Bundle bundle : installed) { 938 if (bundle.getLocation().startsWith(INITIAL_LOCATION)) { 939 if (includeInitial) 940 initial.add(bundle); 941 } else if (!includeInitial && bundle.getBundleId() != 0) 942 initial.add(bundle); 943 } 944 return initial.toArray(new Bundle[initial.size()]); 945 } 946 getBundleByLocation(String location, Bundle[] bundles)947 private static Bundle getBundleByLocation(String location, Bundle[] bundles) { 948 for (Bundle bundle : bundles) { 949 if (location.equalsIgnoreCase(bundle.getLocation())) 950 return bundle; 951 } 952 return null; 953 } 954 uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles, List<Bundle> toRefresh)955 private static void uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles, List<Bundle> toRefresh) { 956 for (Bundle curInitBundle : curInitBundles) { 957 boolean found = false; 958 for (InitialBundle newInitBundle : newInitBundles) { 959 if (curInitBundle.getLocation().equalsIgnoreCase(newInitBundle.locationString)) { 960 found = true; 961 break; 962 } 963 } 964 if (!found) { 965 try { 966 curInitBundle.uninstall(); 967 toRefresh.add(curInitBundle); 968 } catch (BundleException e) { 969 FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_UNINSTALL, curInitBundle.getLocation()), 0, e, null); 970 log.log(entry); 971 } 972 } 973 } 974 } 975 installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, List<Bundle> startBundles, List<Bundle> lazyActivationBundles, List<Bundle> toRefresh)976 private static void installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, List<Bundle> startBundles, List<Bundle> lazyActivationBundles, List<Bundle> toRefresh) { 977 for (InitialBundle initialBundle : initialBundles) { 978 Bundle osgiBundle = getBundleByLocation(initialBundle.locationString, curInitBundles); 979 try { 980 // don't need to install if it is already installed 981 if (osgiBundle == null) { 982 InputStream in = LocationHelper.getStream(initialBundle.location); 983 try { 984 osgiBundle = context.installBundle(initialBundle.locationString, in); 985 }catch (BundleException e) { 986 if (e.getType() == BundleException.DUPLICATE_BUNDLE_ERROR) { 987 continue; 988 // TODO should attempt to lookup the existing bundle 989 } 990 throw e; 991 } 992 // only check for lazy activation header if this is a newly installed bundle and is not marked for persistent start 993 if (!initialBundle.start && hasLazyActivationPolicy(osgiBundle)) { 994 lazyActivationBundles.add(osgiBundle); 995 } 996 } 997 // always set the startlevel incase it has changed (bug 111549) 998 // this is a no-op if the level is the same as previous launch. 999 if ((osgiBundle.getState() & Bundle.UNINSTALLED) == 0 && initialBundle.level >= 0) { 1000 osgiBundle.adapt(BundleStartLevel.class).setStartLevel(initialBundle.level); 1001 } 1002 // if this bundle is supposed to be started then add it to the start list 1003 if (initialBundle.start) { 1004 startBundles.add(osgiBundle); 1005 } 1006 // include basic bundles in case they were not resolved before 1007 if ((osgiBundle.getState() & Bundle.INSTALLED) != 0) 1008 toRefresh.add(osgiBundle); 1009 } catch (BundleException | IOException e) { 1010 FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundle.location), 0, e, null); 1011 log.log(entry); 1012 } 1013 } 1014 } 1015 1016 @SuppressWarnings("deprecation") hasLazyActivationPolicy(Bundle target)1017 private static boolean hasLazyActivationPolicy(Bundle target) { 1018 // check the bundle manifest to see if it defines a lazy activation policy 1019 Dictionary<String, String> headers = target.getHeaders(""); //$NON-NLS-1$ 1020 // first check to see if this is a fragment bundle 1021 String fragmentHost = headers.get(Constants.FRAGMENT_HOST); 1022 if (fragmentHost != null) 1023 return false; // do not activate fragment bundles 1024 // look for the OSGi defined Bundle-ActivationPolicy header 1025 String activationPolicy = headers.get(Constants.BUNDLE_ACTIVATIONPOLICY); 1026 try { 1027 if (activationPolicy != null) { 1028 ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, activationPolicy); 1029 if (elements != null && elements.length > 0) { 1030 // if the value is "lazy" then it has a lazy activation poliyc 1031 if (Constants.ACTIVATION_LAZY.equals(elements[0].getValue())) 1032 return true; 1033 } 1034 } else { 1035 // check for Eclipse specific lazy start headers "Eclipse-LazyStart" and "Eclipse-AutoStart" 1036 String eclipseLazyStart = headers.get(EquinoxModuleDataNamespace.LAZYSTART_HEADER); 1037 if (eclipseLazyStart == null) 1038 eclipseLazyStart = headers.get(EquinoxModuleDataNamespace.AUTOSTART_HEADER); 1039 ManifestElement[] elements = ManifestElement.parseHeader(EquinoxModuleDataNamespace.AUTOSTART_HEADER, eclipseLazyStart); 1040 if (elements != null && elements.length > 0) { 1041 // if the value is true then it is lazy activated 1042 if ("true".equals(elements[0].getValue())) //$NON-NLS-1$ 1043 return true; 1044 // otherwise it is only lazy activated if it defines an exceptions directive. 1045 else if (elements[0].getDirective("exceptions") != null) //$NON-NLS-1$ 1046 return true; 1047 } 1048 } 1049 } catch (BundleException be) { 1050 // ignore this 1051 } 1052 return false; 1053 } 1054 startBundles(Bundle[] startBundles, Bundle[] lazyBundles)1055 private static void startBundles(Bundle[] startBundles, Bundle[] lazyBundles) { 1056 for (Bundle startBundle : startBundles) { 1057 startBundle(startBundle, 0); 1058 } 1059 for (Bundle lazyBundle : lazyBundles) { 1060 startBundle(lazyBundle, Bundle.START_ACTIVATION_POLICY); 1061 } 1062 } 1063 startBundle(Bundle bundle, int options)1064 private static void startBundle(Bundle bundle, int options) { 1065 try { 1066 bundle.start(options); 1067 } catch (BundleException e) { 1068 if ((bundle.getState() & Bundle.RESOLVED) != 0) { 1069 // only log errors if the bundle is resolved 1070 FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_START, bundle.getLocation()), 0, e, null); 1071 log.log(entry); 1072 } 1073 } 1074 } 1075 1076 /** 1077 * Returns a URL which is equivalent to the given URL relative to the 1078 * specified base URL. Works only for file: URLs 1079 * @throws MalformedURLException 1080 */ makeRelative(URL base, URL location)1081 private static URL makeRelative(URL base, URL location) throws MalformedURLException { 1082 if (base == null) 1083 return location; 1084 if (!"file".equals(base.getProtocol())) //$NON-NLS-1$ 1085 return location; 1086 if (!location.getProtocol().equals(REFERENCE_PROTOCOL)) 1087 return location; // we can only make reference urls relative 1088 URL nonReferenceLocation = createURL(location.getPath()); 1089 // if some URL component does not match, return the original location 1090 if (!base.getProtocol().equals(nonReferenceLocation.getProtocol())) 1091 return location; 1092 File locationPath = new File(nonReferenceLocation.getPath()); 1093 // if location is not absolute, return original location 1094 if (!locationPath.isAbsolute()) 1095 return location; 1096 File relativePath = makeRelative(new File(base.getPath()), locationPath); 1097 String urlPath = relativePath.getPath(); 1098 if (File.separatorChar != '/') 1099 urlPath = urlPath.replace(File.separatorChar, '/'); 1100 if (nonReferenceLocation.getPath().endsWith("/")) //$NON-NLS-1$ 1101 // restore original trailing slash 1102 urlPath += '/'; 1103 // couldn't use File to create URL here because it prepends the path with user.dir 1104 URL relativeURL = createURL(base.getProtocol(), base.getHost(), base.getPort(), urlPath); 1105 // now make it back to a reference URL 1106 relativeURL = createURL(REFERENCE_SCHEME + relativeURL.toExternalForm()); 1107 return relativeURL; 1108 } 1109 createURL(String spec)1110 private static URL createURL(String spec) throws MalformedURLException { 1111 return createURL(null, spec); 1112 } 1113 createURL(URL urlContext, String spec)1114 private static URL createURL(URL urlContext, String spec) throws MalformedURLException { 1115 if (context != null && spec.startsWith(REFERENCE_SCHEME)) { 1116 return new URL(urlContext, spec, new Handler(context.getProperty(EquinoxLocations.PROP_INSTALL_AREA))); 1117 } 1118 return new URL(urlContext, spec); 1119 } 1120 createURL(String protocol, String host, String file)1121 private static URL createURL(String protocol, String host, String file) throws MalformedURLException { 1122 return createURL(protocol, host, -1, file); 1123 } 1124 createURL(String protocol, String host, int port, String file)1125 private static URL createURL(String protocol, String host, int port, String file) throws MalformedURLException { 1126 if (context != null && REFERENCE_PROTOCOL.equalsIgnoreCase(protocol)) { 1127 return new URL(protocol, host, port, file, new Handler(context.getProperty(EquinoxLocations.PROP_INSTALL_AREA))); 1128 } 1129 return new URL(protocol, host, port, file); 1130 } 1131 makeRelative(File base, File location)1132 private static File makeRelative(File base, File location) { 1133 if (!location.isAbsolute()) 1134 return location; 1135 File relative = new File(new FilePath(base).makeRelative(new FilePath(location))); 1136 return relative; 1137 } 1138 setStartLevel(final int value)1139 private static void setStartLevel(final int value) throws InterruptedException { 1140 FrameworkStartLevel fwkStartLevel = context.getBundle().adapt(FrameworkStartLevel.class); 1141 final Semaphore semaphore = new Semaphore(0); 1142 StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.STARTLEVEL_CHANGED); 1143 context.addBundleListener(listener); 1144 fwkStartLevel.setStartLevel(value, listener); 1145 updateSplash(semaphore, listener); 1146 } 1147 1148 static class StartupEventListener implements SynchronousBundleListener, FrameworkListener { 1149 private final Semaphore semaphore; 1150 private final int frameworkEventType; 1151 StartupEventListener(Semaphore semaphore, int frameworkEventType)1152 public StartupEventListener(Semaphore semaphore, int frameworkEventType) { 1153 this.semaphore = semaphore; 1154 this.frameworkEventType = frameworkEventType; 1155 } 1156 1157 @Override bundleChanged(BundleEvent event)1158 public void bundleChanged(BundleEvent event) { 1159 if (event.getBundle().getBundleId() == 0 && event.getType() == BundleEvent.STOPPING) 1160 semaphore.release(); 1161 } 1162 1163 @Override frameworkEvent(FrameworkEvent event)1164 public void frameworkEvent(FrameworkEvent event) { 1165 if (event.getType() == frameworkEventType) 1166 semaphore.release(); 1167 } 1168 1169 } 1170 updateSplash(Semaphore semaphore, StartupEventListener listener)1171 private static void updateSplash(Semaphore semaphore, StartupEventListener listener) throws InterruptedException { 1172 ServiceTracker<StartupMonitor, StartupMonitor> monitorTracker = new ServiceTracker<>(context, StartupMonitor.class.getName(), null); 1173 try { 1174 monitorTracker.open(); 1175 } catch (IllegalStateException e) { 1176 // do nothing; this can happen if the framework shutdown 1177 return; 1178 } 1179 try { 1180 while (true) { 1181 StartupMonitor monitor = monitorTracker.getService(); 1182 if (monitor != null) { 1183 try { 1184 monitor.update(); 1185 } catch (Throwable e) { 1186 // ignore exceptions thrown by the monitor 1187 } 1188 } 1189 // can we acquire the semaphore yet? 1190 if (semaphore.tryAcquire(50, TimeUnit.MILLISECONDS)) 1191 break; //done 1192 //else still working, spin another update 1193 } 1194 } finally { 1195 if (listener != null) { 1196 try { 1197 context.removeBundleListener(listener); 1198 monitorTracker.close(); 1199 } catch (IllegalStateException e) { 1200 // do nothing; this can happen if the framework shutdown 1201 } 1202 } 1203 } 1204 } 1205 1206 /** 1207 * Searches for the given target directory immediately under 1208 * the given start location. If one is found then this location is returned; 1209 * otherwise an exception is thrown. 1210 * 1211 * @return the location where target directory was found 1212 * @param start the location to begin searching 1213 */ searchFor(final String target, String start)1214 private static String searchFor(final String target, String start) { 1215 String[] candidates = searchCandidates.get(start); 1216 if (candidates == null) { 1217 File startFile = new File(start); 1218 startFile = LocationHelper.decodePath(startFile); 1219 candidates = startFile.list(); 1220 if (candidates != null) 1221 searchCandidates.put(start, candidates); 1222 } 1223 if (candidates == null) 1224 return null; 1225 String result = null; 1226 Object[] maxVersion = null; 1227 boolean resultIsFile = false; 1228 for (String candidateName : candidates) { 1229 if (!candidateName.startsWith(target)) 1230 continue; 1231 boolean simpleJar = false; 1232 final char versionSep = candidateName.length() > target.length() ? candidateName.charAt(target.length()) : 0; 1233 if (candidateName.length() > target.length() && versionSep != '_' && versionSep != '-') { 1234 // make sure this is not just a jar with no (_|-)version tacked on the end 1235 if (candidateName.length() == 4 + target.length() && candidateName.endsWith(".jar")) //$NON-NLS-1$ 1236 simpleJar = true; 1237 else 1238 // name does not match the target properly with an (_|-) version at the end 1239 continue; 1240 } 1241 // Note: directory with version suffix is always > than directory without version suffix 1242 String version = candidateName.length() > target.length() + 1 && (versionSep == '_' || versionSep == '-') ? candidateName.substring(target.length() + 1) : ""; //$NON-NLS-1$ 1243 Object[] currentVersion = getVersionElements(version); 1244 if (currentVersion != null && compareVersion(maxVersion, currentVersion) < 0) { 1245 File candidate = new File(start, candidateName); 1246 boolean candidateIsFile = candidate.isFile(); 1247 // if simple jar; make sure it is really a file before accepting it 1248 if (!simpleJar || candidateIsFile) { 1249 result = candidate.getAbsolutePath(); 1250 resultIsFile = candidateIsFile; 1251 maxVersion = currentVersion; 1252 } 1253 } 1254 } 1255 if (result == null) 1256 return null; 1257 return result.replace(File.separatorChar, '/') + (resultIsFile ? "" : "/"); //$NON-NLS-1$ //$NON-NLS-2$ 1258 } 1259 1260 /** 1261 * Do a quick parse of version identifier so its elements can be correctly compared. 1262 * If we are unable to parse the full version, remaining elements are initialized 1263 * with suitable defaults. 1264 * @return an array of size 4; first three elements are of type Integer (representing 1265 * major, minor and service) and the fourth element is of type String (representing 1266 * qualifier). A value of null is returned if there are no valid Integers. Note, that 1267 * returning anything else will cause exceptions in the caller. 1268 */ getVersionElements(String version)1269 private static Object[] getVersionElements(String version) { 1270 Object[] result = {Integer.valueOf(-1), Integer.valueOf(-1), Integer.valueOf(-1), ""}; //$NON-NLS-1$ 1271 StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$ 1272 String token; 1273 for (int i = 0; t.hasMoreTokens() && i < 4; i++) { 1274 token = t.nextToken(); 1275 if (i < 3) { 1276 // major, minor or service ... numeric values 1277 try { 1278 result[i] = Integer.valueOf(token); 1279 } catch (Exception e) { 1280 if (i == 0) 1281 return null; // return null if no valid numbers are present 1282 // invalid number format - use default numbers (-1) for the rest 1283 break; 1284 } 1285 } else { 1286 // qualifier ... string value 1287 result[i] = token; 1288 } 1289 } 1290 return result; 1291 } 1292 1293 /** 1294 * Compares version strings. 1295 * @return result of comparison, as integer; 1296 * <code><0</code> if left < right; 1297 * <code>0</code> if left == right; 1298 * <code>>0</code> if left > right; 1299 */ compareVersion(Object[] left, Object[] right)1300 private static int compareVersion(Object[] left, Object[] right) { 1301 if (left == null) 1302 return -1; 1303 int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major 1304 if (result != 0) 1305 return result; 1306 1307 result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor 1308 if (result != 0) 1309 return result; 1310 1311 result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service 1312 if (result != 0) 1313 return result; 1314 1315 return ((String) left[3]).compareTo((String) right[3]); // compare qualifier 1316 } 1317 1318 private static class InitialBundle { 1319 public final String locationString; 1320 public final URL location; 1321 public final int level; 1322 public final boolean start; 1323 InitialBundle(String locationString, URL location, int level, boolean start)1324 InitialBundle(String locationString, URL location, int level, boolean start) { 1325 this.locationString = locationString; 1326 this.location = location; 1327 this.level = level; 1328 this.start = start; 1329 } 1330 } 1331 1332 /** 1333 * Sets the initial properties for the platform. 1334 * This method must be called before calling the {@link #run(String[], Runnable)} or 1335 * {@link #startup(String[], Runnable)} methods for the properties to be used in 1336 * a launched instance of the platform. 1337 * <p> 1338 * If the specified properties contains a null value then the key for that value 1339 * will be cleared from the properties of the platform. 1340 * </p> 1341 * @param initialProperties the initial properties to set for the platform. 1342 * @since 3.2 1343 */ setInitialProperties(Map<String, String> initialProperties)1344 public static void setInitialProperties(Map<String, String> initialProperties) { 1345 if (initialProperties == null || initialProperties.isEmpty()) 1346 return; 1347 for (Map.Entry<String, String> entry : initialProperties.entrySet()) { 1348 if (entry.getValue() != null) 1349 setProperty(entry.getKey(), entry.getValue()); 1350 else 1351 clearProperty(entry.getKey()); 1352 } 1353 } 1354 1355 /** 1356 * Returns the context of the system bundle. A value of 1357 * <code>null</code> is returned if the platform is not running. 1358 * @return the context of the system bundle 1359 * @throws java.lang.SecurityException If the caller does not have the 1360 * appropriate <code>AdminPermission[system.bundle,CONTEXT]</code>, and 1361 * the Java Runtime Environment supports permissions. 1362 */ getSystemBundleContext()1363 public static BundleContext getSystemBundleContext() { 1364 if (context == null || !running) 1365 return null; 1366 return context.getBundle().getBundleContext(); 1367 } 1368 isForcedRestart()1369 private static boolean isForcedRestart() { 1370 return Boolean.valueOf(getProperty(EquinoxConfiguration.PROP_FORCED_RESTART)).booleanValue(); 1371 } 1372 1373 /* 1374 * NOTE: This is an internal/experimental method used by launchers that need to react when the framework 1375 * is shutdown internally. 1376 * 1377 * Adds a framework shutdown handler. <p> 1378 * A handler implements the {@link Runnable} interface. When the framework is shutdown 1379 * the {@link Runnable#run()} method is called for each registered handler. Handlers should 1380 * make no assumptions on the thread it is being called from. If a handler object is 1381 * registered multiple times it will be called once for each registration. 1382 * <p> 1383 * At the time a handler is called the framework is shutdown. Handlers must not depend on 1384 * a running framework to execute or attempt to load additional classes from bundles 1385 * installed in the framework. 1386 * @param handler the framework shutdown handler 1387 * @throws IllegalStateException if the platform is already running 1388 */ internalAddFrameworkShutdownHandler(Runnable handler)1389 static void internalAddFrameworkShutdownHandler(Runnable handler) { 1390 if (running) 1391 throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING); 1392 1393 if (shutdownHandlers == null) 1394 shutdownHandlers = new ArrayList<>(); 1395 1396 shutdownHandlers.add(handler); 1397 } 1398 1399 /* 1400 * NOTE: This is an internal/experimental method used by launchers that need to react when the framework 1401 * is shutdown internally. 1402 * 1403 * Removes a framework shutdown handler. <p> 1404 * @param handler the framework shutdown handler 1405 * @throws IllegalStateException if the platform is already running 1406 */ internalRemoveFrameworkShutdownHandler(Runnable handler)1407 static void internalRemoveFrameworkShutdownHandler(Runnable handler) { 1408 if (running) 1409 throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING); 1410 1411 if (shutdownHandlers != null) 1412 shutdownHandlers.remove(handler); 1413 } 1414 registerFrameworkShutdownHandlers()1415 private static void registerFrameworkShutdownHandlers() { 1416 if (shutdownHandlers == null) 1417 return; 1418 1419 final Bundle systemBundle = context.getBundle(); 1420 for (Iterator<Runnable> it = shutdownHandlers.iterator(); it.hasNext();) { 1421 final Runnable handler = it.next(); 1422 BundleListener listener = new BundleListener() { 1423 @Override 1424 public void bundleChanged(BundleEvent event) { 1425 if (event.getBundle() == systemBundle && event.getType() == BundleEvent.STOPPED) { 1426 handler.run(); 1427 } 1428 } 1429 }; 1430 context.addBundleListener(listener); 1431 } 1432 } 1433 } 1434