| /******************************************************************************* |
| * Copyright (c) 2003, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Alex Blewitt (bug 172969) |
| *******************************************************************************/ |
| package org.eclipse.core.runtime.adaptor; |
| |
| import java.io.*; |
| import java.lang.reflect.Method; |
| import java.net.*; |
| import java.security.CodeSource; |
| import java.security.ProtectionDomain; |
| import java.util.*; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| import org.eclipse.core.runtime.internal.adaptor.*; |
| import org.eclipse.osgi.container.Module; |
| import org.eclipse.osgi.container.ModuleRevision; |
| import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace; |
| import org.eclipse.osgi.framework.log.FrameworkLog; |
| import org.eclipse.osgi.framework.log.FrameworkLogEntry; |
| import org.eclipse.osgi.framework.util.FilePath; |
| import org.eclipse.osgi.internal.debug.Debug; |
| import org.eclipse.osgi.internal.framework.EquinoxConfiguration; |
| import org.eclipse.osgi.internal.framework.EquinoxContainer; |
| import org.eclipse.osgi.internal.location.EquinoxLocations; |
| import org.eclipse.osgi.internal.location.LocationHelper; |
| import org.eclipse.osgi.internal.messages.Msg; |
| import org.eclipse.osgi.launch.Equinox; |
| import org.eclipse.osgi.report.resolution.ResolutionReport; |
| import org.eclipse.osgi.service.datalocation.Location; |
| import org.eclipse.osgi.service.environment.EnvironmentInfo; |
| import org.eclipse.osgi.service.runnable.ApplicationLauncher; |
| import org.eclipse.osgi.service.runnable.StartupMonitor; |
| import org.eclipse.osgi.storage.url.reference.Handler; |
| import org.eclipse.osgi.util.ManifestElement; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.*; |
| import org.osgi.framework.launch.Framework; |
| import org.osgi.framework.startlevel.BundleStartLevel; |
| import org.osgi.framework.startlevel.FrameworkStartLevel; |
| import org.osgi.framework.wiring.FrameworkWiring; |
| import org.osgi.resource.Resource; |
| import org.osgi.util.tracker.ServiceTracker; |
| |
| /** |
| * Special startup class for the Eclipse Platform. This class cannot be |
| * instantiated; all functionality is provided by static methods. |
| * <p> |
| * The Eclipse Platform makes heavy use of Java class loaders for loading |
| * plug-ins. Even the Eclipse Runtime itself and the OSGi framework need |
| * to be loaded by special class loaders. The upshot is that a |
| * client program (such as a Java main program, a servlet) cannot |
| * reference any part of Eclipse directly. Instead, a client must use this |
| * loader class to start the platform, invoking functionality defined |
| * in plug-ins, and shutting down the platform when done. |
| * </p> |
| * <p>Note that the fields on this class are not API. </p> |
| * @since 3.0 |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class EclipseStarter { |
| private static BundleContext context; |
| private static boolean initialize = false; |
| public static boolean debug = false; |
| private static boolean running = false; |
| private static ServiceRegistration<?> defaultMonitorRegistration = null; |
| private static ServiceRegistration<?> appLauncherRegistration = null; |
| private static ServiceRegistration<?> splashStreamRegistration = null; |
| |
| // command line arguments |
| private static final String CLEAN = "-clean"; //$NON-NLS-1$ |
| private static final String CONSOLE = "-console"; //$NON-NLS-1$ |
| private static final String CONSOLE_LOG = "-consoleLog"; //$NON-NLS-1$ |
| private static final String DEBUG = "-debug"; //$NON-NLS-1$ |
| private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$ |
| private static final String DEV = "-dev"; //$NON-NLS-1$ |
| private static final String WS = "-ws"; //$NON-NLS-1$ |
| private static final String OS = "-os"; //$NON-NLS-1$ |
| private static final String ARCH = "-arch"; //$NON-NLS-1$ |
| private static final String NL = "-nl"; //$NON-NLS-1$ |
| private static final String NL_EXTENSIONS = "-nlExtensions"; //$NON-NLS-1$ |
| private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$ |
| private static final String USER = "-user"; //$NON-NLS-1$ |
| private static final String NOEXIT = "-noExit"; //$NON-NLS-1$ |
| private static final String LAUNCHER = "-launcher"; //$NON-NLS-1$ |
| |
| // this is more of an Eclipse argument but this OSGi implementation stores its |
| // metadata alongside Eclipse's. |
| private static final String DATA = "-data"; //$NON-NLS-1$ |
| |
| // System properties |
| public static final String PROP_BUNDLES = "osgi.bundles"; //$NON-NLS-1$ |
| public static final String PROP_BUNDLES_STARTLEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$ //The start level used to install the bundles |
| public static final String PROP_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$ |
| public static final String PROP_INITIAL_STARTLEVEL = "osgi.startLevel"; //$NON-NLS-1$ //The start level when the fwl start |
| public static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$ |
| public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$ |
| public static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$ |
| public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$ |
| public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass"; //$NON-NLS-1$ |
| public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$ |
| public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$ |
| public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$ |
| public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$ |
| private static final String PROP_NL_EXTENSIONS = "osgi.nl.extensions"; //$NON-NLS-1$ |
| public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$ |
| public static final String PROP_ADAPTOR = "osgi.adaptor"; //$NON-NLS-1$ |
| public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$ |
| public static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$ |
| public static final String PROP_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$ |
| public static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$ |
| public static final String PROP_FRAMEWORK_SHAPE = "osgi.framework.shape"; //$NON-NLS-1$ //the shape of the fwk (jar, or folder) |
| public static final String PROP_NOSHUTDOWN = "osgi.noShutdown"; //$NON-NLS-1$ |
| |
| public static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$ |
| public static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$ |
| public static final String PROP_CONSOLE_LOG = "eclipse.consoleLog"; //$NON-NLS-1$ |
| public static final String PROP_IGNOREAPP = "eclipse.ignoreApp"; //$NON-NLS-1$ |
| public static final String PROP_REFRESH_BUNDLES = "eclipse.refreshBundles"; //$NON-NLS-1$ |
| private static final String PROP_ALLOW_APPRELAUNCH = "eclipse.allowAppRelaunch"; //$NON-NLS-1$ |
| private static final String PROP_APPLICATION_LAUNCHDEFAULT = "eclipse.application.launchDefault"; //$NON-NLS-1$ |
| |
| private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$ |
| private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$ |
| private static final String REFERENCE_PROTOCOL = "reference"; //$NON-NLS-1$ |
| private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$ |
| |
| private static final int DEFAULT_INITIAL_STARTLEVEL = 6; // default value for legacy purposes |
| private static final String DEFAULT_BUNDLES_STARTLEVEL = "4"; //$NON-NLS-1$ |
| |
| private static FrameworkLog log; |
| // directory of serch candidates keyed by directory abs path -> directory listing (bug 122024) |
| private static Map<String, String[]> searchCandidates = new HashMap<>(4); |
| private static EclipseAppLauncher appLauncher; |
| private static List<Runnable> shutdownHandlers; |
| |
| private static ConsoleManager consoleMgr = null; |
| |
| private static Map<String, String> configuration = null; |
| private static Framework framework = null; |
| private static EquinoxConfiguration equinoxConfig; |
| private static String[] allArgs = null; |
| private static String[] frameworkArgs = null; |
| private static String[] appArgs = null; |
| |
| private synchronized static String getProperty(String key) { |
| if (equinoxConfig != null) { |
| return equinoxConfig.getConfiguration(key); |
| } |
| return getConfiguration().get(key); |
| } |
| |
| private synchronized static String getProperty(String key, String dft) { |
| if (equinoxConfig != null) { |
| return equinoxConfig.getConfiguration(key, dft); |
| } |
| String result = getConfiguration().get(key); |
| return result == null ? dft : result; |
| } |
| |
| private synchronized static Object setProperty(String key, String value) { |
| if (equinoxConfig != null) { |
| return equinoxConfig.setProperty(key, value); |
| } |
| if ("true".equals(getConfiguration().get(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES))) { //$NON-NLS-1$ |
| System.setProperty(key, value); |
| } |
| return getConfiguration().put(key, value); |
| } |
| |
| private synchronized static Object clearProperty(String key) { |
| if (equinoxConfig != null) { |
| return equinoxConfig.clearConfiguration(key); |
| } |
| return getConfiguration().remove(key); |
| } |
| |
| private synchronized static Map<String, String> getConfiguration() { |
| if (configuration == null) { |
| configuration = new HashMap<>(); |
| // TODO hack to set these to defaults for EclipseStarter |
| // Note that this hack does not allow this property to be specified in config.ini |
| configuration.put(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES, System.getProperty(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES, "true")); //$NON-NLS-1$ |
| // we handle this compatibility setting special for EclipseStarter |
| String systemCompatibilityBoot = System.getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION); |
| if (systemCompatibilityBoot != null) { |
| // The system properties have a specific setting; use it |
| configuration.put(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION, systemCompatibilityBoot); |
| } else { |
| // set a default value; but this value can be overriden by the config.ini |
| configuration.put(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION + EquinoxConfiguration.PROP_DEFAULT_SUFFIX, "true"); //$NON-NLS-1$ |
| } |
| } |
| return configuration; |
| } |
| |
| /** |
| * This is the main to start osgi. |
| * It only works when the framework is being jared as a single jar |
| */ |
| public static void main(String[] args) throws Exception { |
| if (getProperty("eclipse.startTime") == null) //$NON-NLS-1$ |
| setProperty("eclipse.startTime", Long.toString(System.currentTimeMillis())); //$NON-NLS-1$ |
| if (getProperty(PROP_NOSHUTDOWN) == null) |
| setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$ |
| // set the compatibility boot delegation flag to false to get "standard" OSGi behavior WRT boot delegation (bug 178477) |
| if (getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION) == null) |
| setProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION, "false"); //$NON-NLS-1$ |
| Object result = run(args, null); |
| if (result instanceof Integer && !Boolean.valueOf(getProperty(PROP_NOSHUTDOWN)).booleanValue()) |
| System.exit(((Integer) result).intValue()); |
| } |
| |
| /** |
| * Launches the platform and runs a single application. The application is either identified |
| * in the given arguments (e.g., -application <app id>) or in the <code>eclipse.application</code> |
| * System property. This convenience method starts |
| * up the platform, runs the indicated application, and then shuts down the |
| * platform. The platform must not be running already. |
| * |
| * @param args the command line-style arguments used to configure the platform |
| * @param endSplashHandler the block of code to run to tear down the splash |
| * screen or <code>null</code> if no tear down is required |
| * @return the result of running the application |
| * @throws Exception if anything goes wrong |
| */ |
| public static Object run(String[] args, Runnable endSplashHandler) throws Exception { |
| if (running) |
| throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING); |
| boolean startupFailed = true; |
| try { |
| startup(args, endSplashHandler); |
| startupFailed = false; |
| if (Boolean.valueOf(getProperty(PROP_IGNOREAPP)).booleanValue() || isForcedRestart()) |
| return null; |
| return run(null); |
| } catch (Throwable e) { |
| // ensure the splash screen is down |
| if (endSplashHandler != null) |
| endSplashHandler.run(); |
| // may use startupFailed to understand where the error happened |
| FrameworkLogEntry logEntry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, startupFailed ? Msg.ECLIPSE_STARTUP_STARTUP_ERROR : Msg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null); |
| if (log != null) |
| log.log(logEntry); |
| else |
| // TODO desperate measure - ideally, we should write this to disk (a la Main.log) |
| e.printStackTrace(); |
| } finally { |
| try { |
| // The application typically sets the exit code however the framework can request that |
| // it be re-started. We need to check for this and potentially override the exit code. |
| if (isForcedRestart()) |
| setProperty(PROP_EXITCODE, "23"); //$NON-NLS-1$ |
| if (!Boolean.valueOf(getProperty(PROP_NOSHUTDOWN)).booleanValue()) |
| shutdown(); |
| } catch (Throwable e) { |
| FrameworkLogEntry logEntry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, Msg.ECLIPSE_STARTUP_SHUTDOWN_ERROR, 1, e, null); |
| if (log != null) |
| log.log(logEntry); |
| else |
| // TODO desperate measure - ideally, we should write this to disk (a la Main.log) |
| e.printStackTrace(); |
| } |
| } |
| // we only get here if an error happened |
| if (getProperty(PROP_EXITCODE) == null) { |
| setProperty(PROP_EXITCODE, "13"); //$NON-NLS-1$ |
| setProperty(PROP_EXITDATA, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_CHECK_LOG, log == null ? null : log.getFile().getPath())); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns true if the platform is already running, false otherwise. |
| * @return whether or not the platform is already running |
| */ |
| public static boolean isRunning() { |
| return running; |
| } |
| |
| /** |
| * Starts the platform and sets it up to run a single application. The application is either identified |
| * in the given arguments (e.g., -application <app id>) or in the <code>eclipse.application</code> |
| * System property. The platform must not be running already. |
| * <p> |
| * The given runnable (if not <code>null</code>) is used to tear down the splash screen if required. |
| * </p> |
| * @param args the arguments passed to the application |
| * @return BundleContext the context of the system bundle |
| * @throws Exception if anything goes wrong |
| */ |
| public static BundleContext startup(String[] args, Runnable endSplashHandler) throws Exception { |
| if (running) |
| throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING); |
| processCommandLine(args); |
| framework = new Equinox(getConfiguration()); |
| framework.init(); |
| context = framework.getBundleContext(); |
| ServiceReference<FrameworkLog> logRef = context.getServiceReference(FrameworkLog.class); |
| log = context.getService(logRef); |
| ServiceReference<EnvironmentInfo> configRef = context.getServiceReference(EnvironmentInfo.class); |
| equinoxConfig = (EquinoxConfiguration) context.getService(configRef); |
| |
| equinoxConfig.setAllArgs(allArgs); |
| equinoxConfig.setFrameworkArgs(frameworkArgs); |
| equinoxConfig.setAppArgs(appArgs); |
| |
| registerFrameworkShutdownHandlers(); |
| publishSplashScreen(endSplashHandler); |
| consoleMgr = ConsoleManager.startConsole(context, equinoxConfig); |
| |
| Bundle[] startBundles = loadBasicBundles(); |
| |
| if (startBundles == null || ("true".equals(getProperty(PROP_REFRESH_BUNDLES)) && refreshPackages(getCurrentBundles(false)))) { //$NON-NLS-1$ |
| waitForShutdown(); |
| return context; // cannot continue; loadBasicBundles caused refreshPackages to shutdown the framework |
| } |
| |
| framework.start(); |
| |
| if (isForcedRestart()) { |
| return context; |
| } |
| // set the framework start level to the ultimate value. This will actually start things |
| // running if they are persistently active. |
| setStartLevel(getStartLevel()); |
| // they should all be active by this time |
| ensureBundlesActive(startBundles); |
| |
| // in the case where the built-in console is disabled we should try to start the console bundle |
| try { |
| consoleMgr.checkForConsoleBundle(); |
| } catch (BundleException e) { |
| FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null); |
| log.log(entry); |
| } |
| // TODO should log unresolved bundles if in debug or dev mode |
| running = true; |
| return context; |
| } |
| |
| private static int getStartLevel() { |
| String level = getProperty(PROP_INITIAL_STARTLEVEL); |
| if (level != null) |
| try { |
| return Integer.parseInt(level); |
| } catch (NumberFormatException e) { |
| if (debug) |
| Debug.println("Start level = " + level + " parsed. Using hardcoded default: 6"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| return DEFAULT_INITIAL_STARTLEVEL; |
| } |
| |
| /** |
| * Runs the application for which the platform was started. The platform |
| * must be running. |
| * <p> |
| * The given argument is passed to the application being run. If it is <code>null</code> |
| * then the command line arguments used in starting the platform, and not consumed |
| * by the platform code, are passed to the application as a <code>String[]</code>. |
| * </p> |
| * @param argument the argument passed to the application. May be <code>null</code> |
| * @return the result of running the application |
| * @throws Exception if anything goes wrong |
| */ |
| public static Object run(Object argument) throws Exception { |
| if (!running) |
| throw new IllegalStateException(Msg.ECLIPSE_STARTUP_NOT_RUNNING); |
| // if we are just initializing, do not run the application just return. |
| if (initialize) |
| return Integer.valueOf(0); |
| try { |
| if (appLauncher == null) { |
| boolean launchDefault = Boolean.valueOf(getProperty(PROP_APPLICATION_LAUNCHDEFAULT, "true")).booleanValue(); //$NON-NLS-1$ |
| // create the ApplicationLauncher and register it as a service |
| appLauncher = new EclipseAppLauncher(context, Boolean.valueOf(getProperty(PROP_ALLOW_APPRELAUNCH)).booleanValue(), launchDefault, log, equinoxConfig); |
| appLauncherRegistration = context.registerService(ApplicationLauncher.class.getName(), appLauncher, null); |
| // must start the launcher AFTER service restration because this method |
| // blocks and runs the application on the current thread. This method |
| // will return only after the application has stopped. |
| return appLauncher.start(argument); |
| } |
| return appLauncher.reStart(argument); |
| } catch (Exception e) { |
| if (log != null && context != null) { // context can be null if OSGi failed to launch (bug 151413) |
| ResolutionReport report = context.getBundle().adapt(Module.class).getContainer().resolve(null, false); |
| for (Resource unresolved : report.getEntries().keySet()) { |
| String bsn = ((ModuleRevision) unresolved).getSymbolicName(); |
| FrameworkLogEntry logEntry = new FrameworkLogEntry(bsn != null ? bsn : EquinoxContainer.NAME, FrameworkLogEntry.WARNING, 0, Msg.Module_ResolveError + report.getResolutionReportMessage(unresolved), 1, null, null); |
| log.log(logEntry); |
| } |
| } |
| throw e; |
| } |
| } |
| |
| /** |
| * Shuts down the Platform. The state of the Platform is not automatically |
| * saved before shutting down. |
| * <p> |
| * On return, the Platform will no longer be running (but could be re-launched |
| * with another call to startup). If relaunching, care must be taken to reinitialize |
| * any System properties which the platform uses (e.g., osgi.instance.area) as |
| * some policies in the platform do not allow resetting of such properties on |
| * subsequent runs. |
| * </p><p> |
| * Any objects handed out by running Platform, |
| * including Platform runnables obtained via getRunnable, will be |
| * permanently invalid. The effects of attempting to invoke methods |
| * on invalid objects is undefined. |
| * </p> |
| * @throws Exception if anything goes wrong |
| */ |
| public static void shutdown() throws Exception { |
| if (!running || framework == null) |
| return; |
| if (framework.getState() == Bundle.ACTIVE) { |
| if (appLauncherRegistration != null) |
| appLauncherRegistration.unregister(); |
| if (splashStreamRegistration != null) |
| splashStreamRegistration.unregister(); |
| if (defaultMonitorRegistration != null) |
| defaultMonitorRegistration.unregister(); |
| } |
| if (appLauncher != null) |
| appLauncher.shutdown(); |
| appLauncherRegistration = null; |
| appLauncher = null; |
| splashStreamRegistration = null; |
| defaultMonitorRegistration = null; |
| if (consoleMgr != null) { |
| consoleMgr.stopConsole(); |
| consoleMgr = null; |
| } |
| if (framework.getState() == Bundle.ACTIVE) { |
| framework.stop(); |
| framework.waitForStop(0); |
| framework = null; |
| } |
| configuration = null; |
| equinoxConfig = null; |
| context = null; |
| running = false; |
| } |
| |
| private static void ensureBundlesActive(Bundle[] bundles) { |
| for (int i = 0; i < bundles.length; i++) { |
| if (bundles[i].getState() != Bundle.ACTIVE) { |
| if (bundles[i].getState() == Bundle.INSTALLED) { |
| // Log that the bundle is not resolved |
| log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundles[i].getLocation()), 0, null, null)); |
| continue; |
| } |
| // check that the startlevel allows the bundle to be active (111550) |
| FrameworkStartLevel fwStartLevel = context.getBundle().adapt(FrameworkStartLevel.class); |
| BundleStartLevel bundleStartLevel = bundles[i].adapt(BundleStartLevel.class); |
| if (fwStartLevel != null && (bundleStartLevel.getStartLevel() <= fwStartLevel.getStartLevel())) { |
| log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_ACTIVE, bundles[i]), 0, null, null)); |
| } |
| } |
| } |
| } |
| |
| private static void publishSplashScreen(final Runnable endSplashHandler) { |
| if (endSplashHandler == null) |
| return; |
| // register the output stream to the launcher if it exists |
| try { |
| Method method = endSplashHandler.getClass().getMethod("getOutputStream", new Class[0]); //$NON-NLS-1$ |
| Object outputStream = method.invoke(endSplashHandler, new Object[0]); |
| if (outputStream instanceof OutputStream) { |
| Dictionary<String, Object> osProperties = new Hashtable<>(); |
| osProperties.put("name", "splashstream"); //$NON-NLS-1$//$NON-NLS-2$ |
| splashStreamRegistration = context.registerService(OutputStream.class.getName(), outputStream, osProperties); |
| } |
| } catch (Exception ex) { |
| // ignore |
| } |
| // keep this splash handler as the default startup monitor |
| try { |
| Dictionary<String, Object> monitorProps = new Hashtable<>(); |
| monitorProps.put(Constants.SERVICE_RANKING, Integer.valueOf(Integer.MIN_VALUE)); |
| defaultMonitorRegistration = context.registerService(StartupMonitor.class.getName(), new DefaultStartupMonitor(endSplashHandler, equinoxConfig), monitorProps); |
| } catch (IllegalStateException e) { |
| //splash handler did not provide the necessary methods, ignore it |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| private static URL searchForBundle(String name, String parent) throws MalformedURLException { |
| URL url = null; |
| File fileLocation = null; |
| boolean reference = false; |
| try { |
| createURL(name); // quick check to see if the name is a valid URL |
| url = createURL(new File(parent).toURL(), name); |
| } catch (MalformedURLException e) { |
| // TODO this is legacy support for non-URL names. It should be removed eventually. |
| // if name was not a URL then construct one. |
| // Assume it should be a reference and that it is relative. This support need not |
| // be robust as it is temporary.. |
| File child = new File(name); |
| fileLocation = child.isAbsolute() ? child : new File(parent, name); |
| url = createURL(REFERENCE_PROTOCOL, null, fileLocation.toURL().toExternalForm()); |
| reference = true; |
| } |
| // if the name was a URL then see if it is relative. If so, insert syspath. |
| if (!reference) { |
| URL baseURL = url; |
| // if it is a reference URL then strip off the reference: and set base to the file:... |
| if (url.getProtocol().equals(REFERENCE_PROTOCOL)) { |
| reference = true; |
| String baseSpec = url.getPath(); |
| if (baseSpec.startsWith(FILE_SCHEME)) { |
| File child = new File(baseSpec.substring(5)); |
| baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL(); |
| } else |
| baseURL = createURL(baseSpec); |
| } |
| |
| fileLocation = new File(baseURL.getPath()); |
| // if the location is relative, prefix it with the parent |
| if (!fileLocation.isAbsolute()) |
| fileLocation = new File(parent, fileLocation.toString()); |
| } |
| // If the result is a reference then search for the real result and |
| // reconstruct the answer. |
| if (reference) { |
| String result = searchFor(fileLocation.getName(), new File(fileLocation.getParent()).getAbsolutePath()); |
| if (result != null) |
| url = createURL(REFERENCE_PROTOCOL, null, FILE_SCHEME + result); |
| else |
| return null; |
| } |
| |
| // finally we have something worth trying |
| try { |
| URLConnection result = LocationHelper.getConnection(url); |
| result.connect(); |
| return url; |
| } catch (IOException e) { |
| // int i = location.lastIndexOf('_'); |
| // return i == -1? location : location.substring(0, i); |
| return null; |
| } |
| } |
| |
| /* |
| * Ensure all basic bundles are installed, resolved and scheduled to start. Returns an array containing |
| * all basic bundles that are marked to start. |
| * Returns null if the framework has been shutdown as a result of refreshPackages |
| */ |
| private static Bundle[] loadBasicBundles() throws InterruptedException { |
| long startTime = System.currentTimeMillis(); |
| String osgiBundles = getProperty(PROP_BUNDLES); |
| String osgiExtensions = getProperty(PROP_EXTENSIONS); |
| if (osgiExtensions != null && osgiExtensions.length() > 0) { |
| osgiBundles = osgiExtensions + ',' + osgiBundles; |
| setProperty(PROP_BUNDLES, osgiBundles); |
| } |
| String[] installEntries = getArrayFromList(osgiBundles, ","); //$NON-NLS-1$ |
| // get the initial bundle list from the installEntries |
| InitialBundle[] initialBundles = getInitialBundles(installEntries); |
| // get the list of currently installed initial bundles from the framework |
| Bundle[] curInitBundles = getCurrentBundles(true); |
| |
| // list of bundles to be refreshed |
| List<Bundle> toRefresh = new ArrayList<>(curInitBundles.length); |
| // uninstall any of the currently installed bundles that do not exist in the |
| // initial bundle list from installEntries. |
| uninstallBundles(curInitBundles, initialBundles, toRefresh); |
| |
| // install the initialBundles that are not already installed. |
| List<Bundle> startBundles = new ArrayList<>(installEntries.length); |
| List<Bundle> lazyActivationBundles = new ArrayList<>(installEntries.length); |
| installBundles(initialBundles, curInitBundles, startBundles, lazyActivationBundles, toRefresh); |
| |
| // If we installed/uninstalled something, force a refresh of all installed/uninstalled bundles |
| if (!toRefresh.isEmpty() && refreshPackages(toRefresh.toArray(new Bundle[toRefresh.size()]))) |
| return null; // cannot continue; refreshPackages shutdown the framework |
| |
| // schedule all basic bundles to be started |
| Bundle[] startInitBundles = startBundles.toArray(new Bundle[startBundles.size()]); |
| Bundle[] lazyInitBundles = lazyActivationBundles.toArray(new Bundle[lazyActivationBundles.size()]); |
| startBundles(startInitBundles, lazyInitBundles); |
| |
| if (debug) |
| Debug.println("Time to load bundles: " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$ |
| return startInitBundles; |
| } |
| |
| private static InitialBundle[] getInitialBundles(String[] installEntries) { |
| searchCandidates.clear(); |
| List<InitialBundle> result = new ArrayList<>(installEntries.length); |
| int defaultStartLevel = Integer.parseInt(getProperty(PROP_BUNDLES_STARTLEVEL, DEFAULT_BUNDLES_STARTLEVEL)); |
| String syspath = getSysPath(); |
| // should canonicalize the syspath. |
| try { |
| syspath = new File(syspath).getCanonicalPath(); |
| } catch (IOException ioe) { |
| // do nothing |
| } |
| Collection<ServiceReference<Location>> installLocRef; |
| try { |
| installLocRef = context.getServiceReferences(Location.class, Location.INSTALL_FILTER); |
| } catch (InvalidSyntaxException e) { |
| throw new RuntimeException(e); |
| } |
| Location installLocation = installLocRef == null ? null : context.getService(installLocRef.iterator().next()); |
| if (installLocation == null) { |
| throw new IllegalStateException(Msg.EclipseStarter_InstallLocation); |
| } |
| for (int i = 0; i < installEntries.length; i++) { |
| String name = installEntries[i]; |
| int level = defaultStartLevel; |
| boolean start = false; |
| int index = name.lastIndexOf('@'); |
| if (index >= 0) { |
| String[] attributes = getArrayFromList(name.substring(index + 1, name.length()), ":"); //$NON-NLS-1$ |
| for (int j = 0; j < attributes.length; j++) { |
| String attribute = attributes[j]; |
| if (attribute.equals("start")) //$NON-NLS-1$ |
| start = true; |
| else { |
| try { |
| level = Integer.parseInt(attribute); |
| } catch (NumberFormatException e) { // bug 188089 |
| index = name.length(); |
| continue; |
| } |
| } |
| } |
| name = name.substring(0, index); |
| } |
| try { |
| URL location = searchForBundle(name, syspath); |
| if (location == null) { |
| FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_BUNDLE_NOT_FOUND, installEntries[i]), 0, null, null); |
| log.log(entry); |
| // skip this entry |
| continue; |
| } |
| location = makeRelative(installLocation.getURL(), location); |
| String locationString = INITIAL_LOCATION + location.toExternalForm(); |
| result.add(new InitialBundle(locationString, location, level, start)); |
| } catch (IOException e) { |
| log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null)); |
| } |
| } |
| return result.toArray(new InitialBundle[result.size()]); |
| } |
| |
| // returns true if the refreshPackages operation caused the framework to shutdown |
| private static boolean refreshPackages(Bundle[] bundles) throws InterruptedException { |
| FrameworkWiring frameworkWiring = context.getBundle().adapt(FrameworkWiring.class); |
| if (frameworkWiring == null) |
| return false; |
| Semaphore semaphore = new Semaphore(0); |
| StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.PACKAGES_REFRESHED); |
| context.addBundleListener(listener); |
| frameworkWiring.refreshBundles(Arrays.asList(bundles), listener); |
| updateSplash(semaphore, listener); |
| return isForcedRestart(); |
| } |
| |
| private static void waitForShutdown() { |
| // wait for the system bundle to stop |
| try { |
| framework.waitForStop(0); |
| } catch (InterruptedException e) { |
| Thread.interrupted(); |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static void processCommandLine(String[] args) throws Exception { |
| allArgs = args; |
| if (args.length == 0) { |
| frameworkArgs = args; |
| return; |
| } |
| int[] configArgs = new int[args.length]; |
| configArgs[0] = -1; // need to initialize the first element to something that could not be an index. |
| int configArgIndex = 0; |
| for (int i = 0; i < args.length; i++) { |
| boolean found = false; |
| // check for args without parameters (i.e., a flag arg) |
| |
| // check if debug should be enabled for the entire platform |
| // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), |
| // simply enable debug. Otherwise, assume that that the following arg is |
| // actually the filename of an options file. This will be processed below. |
| if (args[i].equalsIgnoreCase(DEBUG) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ |
| setProperty(PROP_DEBUG, ""); //$NON-NLS-1$ |
| debug = true; |
| found = true; |
| } |
| |
| // check if development mode should be enabled for the entire platform |
| // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -), |
| // simply enable development mode. Otherwise, assume that that the following arg is |
| // actually some additional development time class path entries. This will be processed below. |
| if (args[i].equalsIgnoreCase(DEV) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ |
| setProperty(PROP_DEV, ""); //$NON-NLS-1$ |
| found = true; |
| } |
| |
| // look for the initialization arg |
| if (args[i].equalsIgnoreCase(INITIALIZE)) { |
| initialize = true; |
| found = true; |
| } |
| |
| // look for the clean flag. |
| if (args[i].equalsIgnoreCase(CLEAN)) { |
| setProperty(PROP_CLEAN, "true"); //$NON-NLS-1$ |
| found = true; |
| } |
| |
| // look for the consoleLog flag |
| if (args[i].equalsIgnoreCase(CONSOLE_LOG)) { |
| setProperty(PROP_CONSOLE_LOG, "true"); //$NON-NLS-1$ |
| found = true; |
| } |
| |
| // look for the console with no port. |
| if (args[i].equalsIgnoreCase(CONSOLE) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$ |
| setProperty(PROP_CONSOLE, ""); //$NON-NLS-1$ |
| found = true; |
| } |
| |
| if (args[i].equalsIgnoreCase(NOEXIT)) { |
| setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$ |
| found = true; |
| } |
| |
| if (found) { |
| configArgs[configArgIndex++] = i; |
| continue; |
| } |
| // check for args with parameters. If we are at the last argument or if the next one |
| // has a '-' as the first character, then we can't have an arg with a parm so continue. |
| if (i == args.length - 1 || args[i + 1].startsWith("-")) { //$NON-NLS-1$ |
| continue; |
| } |
| String arg = args[++i]; |
| |
| // look for the console and port. |
| if (args[i - 1].equalsIgnoreCase(CONSOLE)) { |
| setProperty(PROP_CONSOLE, arg); |
| found = true; |
| } |
| |
| // look for the configuration location . |
| if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) { |
| setProperty(EquinoxLocations.PROP_CONFIG_AREA, arg); |
| found = true; |
| } |
| |
| // look for the data location for this instance. |
| if (args[i - 1].equalsIgnoreCase(DATA)) { |
| setProperty(EquinoxLocations.PROP_INSTANCE_AREA, arg); |
| found = true; |
| } |
| |
| // look for the user location for this instance. |
| if (args[i - 1].equalsIgnoreCase(USER)) { |
| setProperty(EquinoxLocations.PROP_USER_AREA, arg); |
| found = true; |
| } |
| |
| // look for the launcher location |
| if (args[i - 1].equalsIgnoreCase(LAUNCHER)) { |
| setProperty(EquinoxLocations.PROP_LAUNCHER, arg); |
| found = true; |
| } |
| // look for the development mode and class path entries. |
| if (args[i - 1].equalsIgnoreCase(DEV)) { |
| setProperty(PROP_DEV, arg); |
| found = true; |
| } |
| |
| // look for the debug mode and option file location. |
| if (args[i - 1].equalsIgnoreCase(DEBUG)) { |
| setProperty(PROP_DEBUG, arg); |
| debug = true; |
| found = true; |
| } |
| |
| // look for the window system. |
| if (args[i - 1].equalsIgnoreCase(WS)) { |
| setProperty(PROP_WS, arg); |
| found = true; |
| } |
| |
| // look for the operating system |
| if (args[i - 1].equalsIgnoreCase(OS)) { |
| setProperty(PROP_OS, arg); |
| found = true; |
| } |
| |
| // look for the system architecture |
| if (args[i - 1].equalsIgnoreCase(ARCH)) { |
| setProperty(PROP_ARCH, arg); |
| found = true; |
| } |
| |
| // look for the nationality/language |
| if (args[i - 1].equalsIgnoreCase(NL)) { |
| setProperty(PROP_NL, arg); |
| found = true; |
| } |
| |
| // look for the locale extensions |
| if (args[i - 1].equalsIgnoreCase(NL_EXTENSIONS)) { |
| setProperty(PROP_NL_EXTENSIONS, arg); |
| found = true; |
| } |
| |
| // done checking for args. Remember where an arg was found |
| if (found) { |
| configArgs[configArgIndex++] = i - 1; |
| configArgs[configArgIndex++] = i; |
| } |
| } |
| |
| // remove all the arguments consumed by this argument parsing |
| if (configArgIndex == 0) { |
| frameworkArgs = new String[0]; |
| appArgs = args; |
| return; |
| } |
| appArgs = new String[args.length - configArgIndex]; |
| frameworkArgs = new String[configArgIndex]; |
| configArgIndex = 0; |
| int j = 0; |
| int k = 0; |
| for (int i = 0; i < args.length; i++) { |
| if (i == configArgs[configArgIndex]) { |
| frameworkArgs[k++] = args[i]; |
| configArgIndex++; |
| } else |
| appArgs[j++] = args[i]; |
| } |
| return; |
| } |
| |
| /** |
| * Returns the result of converting a list of comma-separated tokens into an array |
| * |
| * @return the array of string tokens |
| * @param prop the initial comma-separated string |
| */ |
| private static String[] getArrayFromList(String prop, String separator) { |
| return ManifestElement.getArrayFromList(prop, separator); |
| } |
| |
| protected static String getSysPath() { |
| String result = getProperty(PROP_SYSPATH); |
| if (result != null) |
| return result; |
| result = getSysPathFromURL(getProperty(PROP_FRAMEWORK)); |
| if (result == null) |
| result = getSysPathFromCodeSource(); |
| if (result == null) |
| throw new IllegalStateException("Can not find the system path."); //$NON-NLS-1$ |
| if (Character.isUpperCase(result.charAt(0))) { |
| char[] chars = result.toCharArray(); |
| chars[0] = Character.toLowerCase(chars[0]); |
| result = new String(chars); |
| } |
| setProperty(PROP_SYSPATH, result); |
| return result; |
| } |
| |
| private static String getSysPathFromURL(String urlSpec) { |
| if (urlSpec == null) |
| return null; |
| URL url = LocationHelper.buildURL(urlSpec, false); |
| if (url == null) |
| return null; |
| File fwkFile = LocationHelper.decodePath(new File(url.getPath())); |
| fwkFile = new File(fwkFile.getAbsolutePath()); |
| fwkFile = new File(fwkFile.getParent()); |
| return fwkFile.getAbsolutePath(); |
| } |
| |
| private static String getSysPathFromCodeSource() { |
| ProtectionDomain pd = EclipseStarter.class.getProtectionDomain(); |
| if (pd == null) |
| return null; |
| CodeSource cs = pd.getCodeSource(); |
| if (cs == null) |
| return null; |
| URL url = cs.getLocation(); |
| if (url == null) |
| return null; |
| String result = url.getPath(); |
| if (File.separatorChar == '\\') { |
| // in case on windows the \ is used |
| result = result.replace('\\', '/'); |
| } |
| if (result.endsWith(".jar")) { //$NON-NLS-1$ |
| result = result.substring(0, result.lastIndexOf('/')); |
| if ("folder".equals(getProperty(PROP_FRAMEWORK_SHAPE))) //$NON-NLS-1$ |
| result = result.substring(0, result.lastIndexOf('/')); |
| } else { |
| if (result.endsWith("/")) //$NON-NLS-1$ |
| result = result.substring(0, result.length() - 1); |
| result = result.substring(0, result.lastIndexOf('/')); |
| result = result.substring(0, result.lastIndexOf('/')); |
| } |
| return result; |
| } |
| |
| private static Bundle[] getCurrentBundles(boolean includeInitial) { |
| Bundle[] installed = context.getBundles(); |
| List<Bundle> initial = new ArrayList<>(); |
| for (int i = 0; i < installed.length; i++) { |
| Bundle bundle = installed[i]; |
| if (bundle.getLocation().startsWith(INITIAL_LOCATION)) { |
| if (includeInitial) |
| initial.add(bundle); |
| } else if (!includeInitial && bundle.getBundleId() != 0) |
| initial.add(bundle); |
| } |
| return initial.toArray(new Bundle[initial.size()]); |
| } |
| |
| private static Bundle getBundleByLocation(String location, Bundle[] bundles) { |
| for (int i = 0; i < bundles.length; i++) { |
| Bundle bundle = bundles[i]; |
| if (location.equalsIgnoreCase(bundle.getLocation())) |
| return bundle; |
| } |
| return null; |
| } |
| |
| private static void uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles, List<Bundle> toRefresh) { |
| for (int i = 0; i < curInitBundles.length; i++) { |
| boolean found = false; |
| for (int j = 0; j < newInitBundles.length; j++) { |
| if (curInitBundles[i].getLocation().equalsIgnoreCase(newInitBundles[j].locationString)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) |
| try { |
| curInitBundles[i].uninstall(); |
| toRefresh.add(curInitBundles[i]); |
| } catch (BundleException e) { |
| FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_UNINSTALL, curInitBundles[i].getLocation()), 0, e, null); |
| log.log(entry); |
| } |
| } |
| } |
| |
| private static void installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, List<Bundle> startBundles, List<Bundle> lazyActivationBundles, List<Bundle> toRefresh) { |
| for (int i = 0; i < initialBundles.length; i++) { |
| Bundle osgiBundle = getBundleByLocation(initialBundles[i].locationString, curInitBundles); |
| try { |
| // don't need to install if it is already installed |
| if (osgiBundle == null) { |
| InputStream in = LocationHelper.getStream(initialBundles[i].location); |
| try { |
| osgiBundle = context.installBundle(initialBundles[i].locationString, in); |
| } catch (BundleException e) { |
| if (e.getType() == BundleException.DUPLICATE_BUNDLE_ERROR) { |
| continue; |
| // TODO should attempt to lookup the existing bundle |
| } |
| throw e; |
| } |
| // only check for lazy activation header if this is a newly installed bundle and is not marked for persistent start |
| if (!initialBundles[i].start && hasLazyActivationPolicy(osgiBundle)) |
| lazyActivationBundles.add(osgiBundle); |
| } |
| // always set the startlevel incase it has changed (bug 111549) |
| // this is a no-op if the level is the same as previous launch. |
| if ((osgiBundle.getState() & Bundle.UNINSTALLED) == 0 && initialBundles[i].level >= 0) { |
| osgiBundle.adapt(BundleStartLevel.class).setStartLevel(initialBundles[i].level); |
| } |
| // if this bundle is supposed to be started then add it to the start list |
| if (initialBundles[i].start) |
| startBundles.add(osgiBundle); |
| // include basic bundles in case they were not resolved before |
| if ((osgiBundle.getState() & Bundle.INSTALLED) != 0) |
| toRefresh.add(osgiBundle); |
| } catch (BundleException e) { |
| FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundles[i].location), 0, e, null); |
| log.log(entry); |
| } catch (IOException e) { |
| FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundles[i].location), 0, e, null); |
| log.log(entry); |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| private static boolean hasLazyActivationPolicy(Bundle target) { |
| // check the bundle manifest to see if it defines a lazy activation policy |
| Dictionary<String, String> headers = target.getHeaders(""); //$NON-NLS-1$ |
| // first check to see if this is a fragment bundle |
| String fragmentHost = headers.get(Constants.FRAGMENT_HOST); |
| if (fragmentHost != null) |
| return false; // do not activate fragment bundles |
| // look for the OSGi defined Bundle-ActivationPolicy header |
| String activationPolicy = headers.get(Constants.BUNDLE_ACTIVATIONPOLICY); |
| try { |
| if (activationPolicy != null) { |
| ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, activationPolicy); |
| if (elements != null && elements.length > 0) { |
| // if the value is "lazy" then it has a lazy activation poliyc |
| if (Constants.ACTIVATION_LAZY.equals(elements[0].getValue())) |
| return true; |
| } |
| } else { |
| // check for Eclipse specific lazy start headers "Eclipse-LazyStart" and "Eclipse-AutoStart" |
| String eclipseLazyStart = headers.get(EquinoxModuleDataNamespace.LAZYSTART_HEADER); |
| if (eclipseLazyStart == null) |
| eclipseLazyStart = headers.get(EquinoxModuleDataNamespace.AUTOSTART_HEADER); |
| ManifestElement[] elements = ManifestElement.parseHeader(EquinoxModuleDataNamespace.AUTOSTART_HEADER, eclipseLazyStart); |
| if (elements != null && elements.length > 0) { |
| // if the value is true then it is lazy activated |
| if ("true".equals(elements[0].getValue())) //$NON-NLS-1$ |
| return true; |
| // otherwise it is only lazy activated if it defines an exceptions directive. |
| else if (elements[0].getDirective("exceptions") != null) //$NON-NLS-1$ |
| return true; |
| } |
| } |
| } catch (BundleException be) { |
| // ignore this |
| } |
| return false; |
| } |
| |
| private static void startBundles(Bundle[] startBundles, Bundle[] lazyBundles) { |
| for (int i = 0; i < startBundles.length; i++) |
| startBundle(startBundles[i], 0); |
| for (int i = 0; i < lazyBundles.length; i++) |
| startBundle(lazyBundles[i], Bundle.START_ACTIVATION_POLICY); |
| } |
| |
| private static void startBundle(Bundle bundle, int options) { |
| try { |
| bundle.start(options); |
| } catch (BundleException e) { |
| if ((bundle.getState() & Bundle.RESOLVED) != 0) { |
| // only log errors if the bundle is resolved |
| FrameworkLogEntry entry = new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, NLS.bind(Msg.ECLIPSE_STARTUP_FAILED_START, bundle.getLocation()), 0, e, null); |
| log.log(entry); |
| } |
| } |
| } |
| |
| /** |
| * Returns a URL which is equivalent to the given URL relative to the |
| * specified base URL. Works only for file: URLs |
| * @throws MalformedURLException |
| */ |
| private static URL makeRelative(URL base, URL location) throws MalformedURLException { |
| if (base == null) |
| return location; |
| if (!"file".equals(base.getProtocol())) //$NON-NLS-1$ |
| return location; |
| if (!location.getProtocol().equals(REFERENCE_PROTOCOL)) |
| return location; // we can only make reference urls relative |
| URL nonReferenceLocation = createURL(location.getPath()); |
| // if some URL component does not match, return the original location |
| if (!base.getProtocol().equals(nonReferenceLocation.getProtocol())) |
| return location; |
| File locationPath = new File(nonReferenceLocation.getPath()); |
| // if location is not absolute, return original location |
| if (!locationPath.isAbsolute()) |
| return location; |
| File relativePath = makeRelative(new File(base.getPath()), locationPath); |
| String urlPath = relativePath.getPath(); |
| if (File.separatorChar != '/') |
| urlPath = urlPath.replace(File.separatorChar, '/'); |
| if (nonReferenceLocation.getPath().endsWith("/")) //$NON-NLS-1$ |
| // restore original trailing slash |
| urlPath += '/'; |
| // couldn't use File to create URL here because it prepends the path with user.dir |
| URL relativeURL = createURL(base.getProtocol(), base.getHost(), base.getPort(), urlPath); |
| // now make it back to a reference URL |
| relativeURL = createURL(REFERENCE_SCHEME + relativeURL.toExternalForm()); |
| return relativeURL; |
| } |
| |
| private static URL createURL(String spec) throws MalformedURLException { |
| return createURL(null, spec); |
| } |
| |
| private static URL createURL(URL urlContext, String spec) throws MalformedURLException { |
| if (context != null && spec.startsWith(REFERENCE_SCHEME)) { |
| return new URL(urlContext, spec, new Handler(context.getProperty(EquinoxLocations.PROP_INSTALL_AREA))); |
| } |
| return new URL(urlContext, spec); |
| } |
| |
| private static URL createURL(String protocol, String host, String file) throws MalformedURLException { |
| return createURL(protocol, host, -1, file); |
| } |
| |
| private static URL createURL(String protocol, String host, int port, String file) throws MalformedURLException { |
| if (context != null && REFERENCE_PROTOCOL.equalsIgnoreCase(protocol)) { |
| return new URL(protocol, host, port, file, new Handler(context.getProperty(EquinoxLocations.PROP_INSTALL_AREA))); |
| } |
| return new URL(protocol, host, port, file); |
| } |
| |
| private static File makeRelative(File base, File location) { |
| if (!location.isAbsolute()) |
| return location; |
| File relative = new File(new FilePath(base).makeRelative(new FilePath(location))); |
| return relative; |
| } |
| |
| private static void setStartLevel(final int value) throws InterruptedException { |
| FrameworkStartLevel fwkStartLevel = context.getBundle().adapt(FrameworkStartLevel.class); |
| final Semaphore semaphore = new Semaphore(0); |
| StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.STARTLEVEL_CHANGED); |
| context.addBundleListener(listener); |
| fwkStartLevel.setStartLevel(value, listener); |
| updateSplash(semaphore, listener); |
| } |
| |
| static class StartupEventListener implements SynchronousBundleListener, FrameworkListener { |
| private final Semaphore semaphore; |
| private final int frameworkEventType; |
| |
| public StartupEventListener(Semaphore semaphore, int frameworkEventType) { |
| this.semaphore = semaphore; |
| this.frameworkEventType = frameworkEventType; |
| } |
| |
| public void bundleChanged(BundleEvent event) { |
| if (event.getBundle().getBundleId() == 0 && event.getType() == BundleEvent.STOPPING) |
| semaphore.release(); |
| } |
| |
| public void frameworkEvent(FrameworkEvent event) { |
| if (event.getType() == frameworkEventType) |
| semaphore.release(); |
| } |
| |
| } |
| |
| private static void updateSplash(Semaphore semaphore, StartupEventListener listener) throws InterruptedException { |
| ServiceTracker<StartupMonitor, StartupMonitor> monitorTracker = new ServiceTracker<>(context, StartupMonitor.class.getName(), null); |
| try { |
| monitorTracker.open(); |
| } catch (IllegalStateException e) { |
| // do nothing; this can happen if the framework shutdown |
| return; |
| } |
| try { |
| while (true) { |
| StartupMonitor monitor = monitorTracker.getService(); |
| if (monitor != null) { |
| try { |
| monitor.update(); |
| } catch (Throwable e) { |
| // ignore exceptions thrown by the monitor |
| } |
| } |
| // can we acquire the semaphore yet? |
| if (semaphore.tryAcquire(50, TimeUnit.MILLISECONDS)) |
| break; //done |
| //else still working, spin another update |
| } |
| } finally { |
| if (listener != null) { |
| try { |
| context.removeBundleListener(listener); |
| monitorTracker.close(); |
| } catch (IllegalStateException e) { |
| // do nothing; this can happen if the framework shutdown |
| } |
| } |
| } |
| } |
| |
| /** |
| * Searches for the given target directory immediately under |
| * the given start location. If one is found then this location is returned; |
| * otherwise an exception is thrown. |
| * |
| * @return the location where target directory was found |
| * @param start the location to begin searching |
| */ |
| private static String searchFor(final String target, String start) { |
| String[] candidates = searchCandidates.get(start); |
| if (candidates == null) { |
| File startFile = new File(start); |
| startFile = LocationHelper.decodePath(startFile); |
| candidates = startFile.list(); |
| if (candidates != null) |
| searchCandidates.put(start, candidates); |
| } |
| if (candidates == null) |
| return null; |
| String result = null; |
| Object[] maxVersion = null; |
| boolean resultIsFile = false; |
| for (int i = 0; i < candidates.length; i++) { |
| String candidateName = candidates[i]; |
| if (!candidateName.startsWith(target)) |
| continue; |
| boolean simpleJar = false; |
| final char versionSep = candidateName.length() > target.length() ? candidateName.charAt(target.length()) : 0; |
| if (candidateName.length() > target.length() && versionSep != '_' && versionSep != '-') { |
| // make sure this is not just a jar with no (_|-)version tacked on the end |
| if (candidateName.length() == 4 + target.length() && candidateName.endsWith(".jar")) //$NON-NLS-1$ |
| simpleJar = true; |
| else |
| // name does not match the target properly with an (_|-) version at the end |
| continue; |
| } |
| // Note: directory with version suffix is always > than directory without version suffix |
| String version = candidateName.length() > target.length() + 1 && (versionSep == '_' || versionSep == '-') ? candidateName.substring(target.length() + 1) : ""; //$NON-NLS-1$ |
| Object[] currentVersion = getVersionElements(version); |
| if (currentVersion != null && compareVersion(maxVersion, currentVersion) < 0) { |
| File candidate = new File(start, candidateName); |
| boolean candidateIsFile = candidate.isFile(); |
| // if simple jar; make sure it is really a file before accepting it |
| if (!simpleJar || candidateIsFile) { |
| result = candidate.getAbsolutePath(); |
| resultIsFile = candidateIsFile; |
| maxVersion = currentVersion; |
| } |
| } |
| } |
| if (result == null) |
| return null; |
| return result.replace(File.separatorChar, '/') + (resultIsFile ? "" : "/"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** |
| * Do a quick parse of version identifier so its elements can be correctly compared. |
| * If we are unable to parse the full version, remaining elements are initialized |
| * with suitable defaults. |
| * @return an array of size 4; first three elements are of type Integer (representing |
| * major, minor and service) and the fourth element is of type String (representing |
| * qualifier). A value of null is returned if there are no valid Integers. Note, that |
| * returning anything else will cause exceptions in the caller. |
| */ |
| private static Object[] getVersionElements(String version) { |
| Object[] result = {Integer.valueOf(-1), Integer.valueOf(-1), Integer.valueOf(-1), ""}; //$NON-NLS-1$ |
| StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$ |
| String token; |
| for (int i = 0; t.hasMoreTokens() && i < 4; i++) { |
| token = t.nextToken(); |
| if (i < 3) { |
| // major, minor or service ... numeric values |
| try { |
| result[i] = Integer.valueOf(token); |
| } catch (Exception e) { |
| if (i == 0) |
| return null; // return null if no valid numbers are present |
| // invalid number format - use default numbers (-1) for the rest |
| break; |
| } |
| } else { |
| // qualifier ... string value |
| result[i] = token; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Compares version strings. |
| * @return result of comparison, as integer; |
| * <code><0</code> if left < right; |
| * <code>0</code> if left == right; |
| * <code>>0</code> if left > right; |
| */ |
| private static int compareVersion(Object[] left, Object[] right) { |
| if (left == null) |
| return -1; |
| int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major |
| if (result != 0) |
| return result; |
| |
| result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor |
| if (result != 0) |
| return result; |
| |
| result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service |
| if (result != 0) |
| return result; |
| |
| return ((String) left[3]).compareTo((String) right[3]); // compare qualifier |
| } |
| |
| private static class InitialBundle { |
| public final String locationString; |
| public final URL location; |
| public final int level; |
| public final boolean start; |
| |
| InitialBundle(String locationString, URL location, int level, boolean start) { |
| this.locationString = locationString; |
| this.location = location; |
| this.level = level; |
| this.start = start; |
| } |
| } |
| |
| /** |
| * Sets the initial properties for the platform. |
| * This method must be called before calling the {@link #run(String[], Runnable)} or |
| * {@link #startup(String[], Runnable)} methods for the properties to be used in |
| * a launched instance of the platform. |
| * <p> |
| * If the specified properties contains a null value then the key for that value |
| * will be cleared from the properties of the platform. |
| * </p> |
| * @param initialProperties the initial properties to set for the platform. |
| * @since 3.2 |
| */ |
| public static void setInitialProperties(Map<String, String> initialProperties) { |
| if (initialProperties == null || initialProperties.isEmpty()) |
| return; |
| for (Map.Entry<String, String> entry : initialProperties.entrySet()) { |
| if (entry.getValue() != null) |
| setProperty(entry.getKey(), entry.getValue()); |
| else |
| clearProperty(entry.getKey()); |
| } |
| } |
| |
| /** |
| * Returns the context of the system bundle. A value of |
| * <code>null</code> is returned if the platform is not running. |
| * @return the context of the system bundle |
| * @throws java.lang.SecurityException If the caller does not have the |
| * appropriate <code>AdminPermission[system.bundle,CONTEXT]</code>, and |
| * the Java Runtime Environment supports permissions. |
| */ |
| public static BundleContext getSystemBundleContext() { |
| if (context == null || !running) |
| return null; |
| return context.getBundle().getBundleContext(); |
| } |
| |
| private static boolean isForcedRestart() { |
| return Boolean.valueOf(getProperty(EquinoxConfiguration.PROP_FORCED_RESTART)).booleanValue(); |
| } |
| |
| /* |
| * NOTE: This is an internal/experimental method used by launchers that need to react when the framework |
| * is shutdown internally. |
| * |
| * Adds a framework shutdown handler. <p> |
| * A handler implements the {@link Runnable} interface. When the framework is shutdown |
| * the {@link Runnable#run()} method is called for each registered handler. Handlers should |
| * make no assumptions on the thread it is being called from. If a handler object is |
| * registered multiple times it will be called once for each registration. |
| * <p> |
| * At the time a handler is called the framework is shutdown. Handlers must not depend on |
| * a running framework to execute or attempt to load additional classes from bundles |
| * installed in the framework. |
| * @param handler the framework shutdown handler |
| * @throws IllegalStateException if the platform is already running |
| */ |
| static void internalAddFrameworkShutdownHandler(Runnable handler) { |
| if (running) |
| throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING); |
| |
| if (shutdownHandlers == null) |
| shutdownHandlers = new ArrayList<>(); |
| |
| shutdownHandlers.add(handler); |
| } |
| |
| /* |
| * NOTE: This is an internal/experimental method used by launchers that need to react when the framework |
| * is shutdown internally. |
| * |
| * Removes a framework shutdown handler. <p> |
| * @param handler the framework shutdown handler |
| * @throws IllegalStateException if the platform is already running |
| */ |
| static void internalRemoveFrameworkShutdownHandler(Runnable handler) { |
| if (running) |
| throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ALREADY_RUNNING); |
| |
| if (shutdownHandlers != null) |
| shutdownHandlers.remove(handler); |
| } |
| |
| private static void registerFrameworkShutdownHandlers() { |
| if (shutdownHandlers == null) |
| return; |
| |
| final Bundle systemBundle = context.getBundle(); |
| for (Iterator<Runnable> it = shutdownHandlers.iterator(); it.hasNext();) { |
| final Runnable handler = it.next(); |
| BundleListener listener = new BundleListener() { |
| public void bundleChanged(BundleEvent event) { |
| if (event.getBundle() == systemBundle && event.getType() == BundleEvent.STOPPED) { |
| handler.run(); |
| } |
| } |
| }; |
| context.addBundleListener(listener); |
| } |
| } |
| } |