| /******************************************************************************* |
| * Copyright (c) 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.runtime.adaptor; |
| |
| import java.io.*; |
| import java.lang.reflect.Constructor; |
| import java.net.*; |
| import java.util.*; |
| import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; |
| import org.eclipse.osgi.framework.internal.core.OSGi; |
| import org.osgi.framework.*; |
| import org.osgi.service.packageadmin.PackageAdmin; |
| import org.osgi.service.startlevel.StartLevel; |
| import org.eclipse.osgi.framework.tracker.ServiceTracker; |
| |
| public class EclipseStarter { |
| |
| private static BundleContext context; |
| private static String dataLocation = null; |
| private static String configLocation = null; |
| |
| // command line arguments |
| private static final String CONSOLE = "-console"; //$NON-NLS-1$ |
| private static final String DEBUG = "-debug"; //$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 CONFIGURATION = "-configuration"; //$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$ |
| |
| |
| // Constants for configuration location discovery |
| private static final String ECLIPSE = "eclipse"; //$NON-NLS-1$ |
| private static final String PRODUCT_SITE_MARKER = ".eclipseproduct"; //$NON-NLS-1$ |
| private static final String PRODUCT_SITE_ID = "id"; //$NON-NLS-1$ |
| private static final String PRODUCT_SITE_VERSION = "version"; //$NON-NLS-1$ |
| |
| /** string containing the classname of the adaptor to be used in this framework instance */ |
| protected static String adaptorClassName = "org.eclipse.core.runtime.adaptor.EclipseAdaptor"; |
| |
| // Console information |
| protected static final String consoleClassName = "org.eclipse.osgi.framework.internal.core.FrameworkConsole"; |
| private static final String CONSOLE_NAME = "OSGi Console"; |
| private static String consolePort = ""; |
| private static boolean console = false; |
| private static ServiceTracker applicationTracker; |
| |
| public static Object run(String[] args,Runnable endSplashHandler) throws Exception { |
| processCommandLine(args); |
| if (System.getProperty("osgi.bundles") == null) |
| System.getProperties().put("osgi.bundles", "org.eclipse.osgi.services_3.0.0@1,org.eclipse.osgi.util_3.0.0@1,org.eclipse.core.runtime.osgi_3.0.0@2,org.eclipse.update.configurator_3.0.0@3,org.eclipse.core.applicationrunner_3.0.0@5"); |
| setInstanceLocation(); |
| setConfigurationLocation(); |
| FrameworkAdaptor adaptor = null; |
| adaptor = createAdaptor(); |
| OSGi osgi = new OSGi(adaptor); |
| if (osgi == null) |
| throw new IllegalStateException("OSGi framework could not be started"); |
| osgi.launch(); |
| |
| try { |
| if (console) |
| startConsole(osgi, new String[0]); |
| |
| context = osgi.getBundleContext(); |
| publishSplashScreen(endSplashHandler); |
| |
| initializeApplicationTracker(); |
| loadBundles(); |
| // TODO determine how long to wait here |
| Runnable application = (Runnable)applicationTracker.waitForService(9999999999L); |
| applicationTracker.close(); |
| if (application == null) |
| throw new IllegalStateException("Unable to acquire application service"); |
| application.run(); |
| } finally { |
| Bundle systemBundle = context.getBundle(0); |
| if (systemBundle.getState() == Bundle.ACTIVE) { |
| final Semaphore semaphore = new Semaphore(0); |
| FrameworkListener listener = new FrameworkListener() { |
| public void frameworkEvent(FrameworkEvent event) { |
| if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED) |
| semaphore.release(); |
| } |
| |
| }; |
| context.addFrameworkListener(listener); |
| systemBundle.stop(); |
| semaphore.acquire(); |
| context.removeFrameworkListener(listener); |
| } |
| } |
| // TODO for now, if an exception is not thrown from this method, we have to do |
| // the System.exit. In the future we will update startup.jar to do the System.exit all |
| // the time. |
| String exitCode = System.getProperty("eclipse.exitcode"); |
| if (exitCode == null) |
| System.exit(0); |
| try { |
| System.exit(Integer.parseInt(exitCode)); |
| } catch (NumberFormatException e) { |
| System.exit(13); |
| } |
| return null; |
| } |
| |
| private static void publishSplashScreen(Runnable endSplashHandler) { |
| // InternalPlatform now how to retrieve this later |
| Dictionary properties = new Hashtable(); |
| properties.put("name","splashscreen"); |
| context.registerService(Runnable.class.getName(),endSplashHandler,properties); |
| } |
| |
| private static String searchForBundle(String name, String syspath) { |
| String location = "reference:file:"+ syspath +"/"+name; |
| try { |
| URLConnection result = new URL(location).openConnection(); |
| result.connect(); |
| return location; |
| } catch (IOException e) { |
| int i = location.indexOf('_'); |
| return i == -1? location : location.substring(0, i); |
| } |
| } |
| |
| private static String[] loadBundles() { |
| long startTime = System.currentTimeMillis(); |
| ServiceReference reference = context.getServiceReference(StartLevel.class.getName()); |
| StartLevel start = null; |
| if (reference != null) |
| start = (StartLevel)context.getService(reference); |
| String[] bundles = getArrayFromList(System.getProperty("osgi.bundles")); |
| |
| String syspath = getSysPath(); |
| Bundle bundle; |
| Vector installed = new Vector(); |
| Vector ignored = new Vector(); |
| for (int i = 0; i < bundles.length; i++) { |
| String name = bundles[i]; |
| if (name == null) |
| continue; |
| try { |
| int level = -1; |
| int index = name.indexOf('@'); |
| if (index >= 0) { |
| String levelString = name.substring(index + 1, name.length()); |
| level = Integer.parseInt(levelString); |
| name = name.substring(0, index); |
| } |
| String location = searchForBundle(name, syspath); |
| if (!isInstalled(location)) { |
| bundle = context.installBundle(location); |
| if (level >= 0 && start != null) |
| start.setBundleStartLevel(bundle, level); |
| installed.addElement(bundle); |
| } else |
| ignored.addElement(name); |
| } catch (Exception ex) { |
| System.err.println("Ignoring " + name); |
| ex.printStackTrace(); |
| ignored.addElement(name); |
| continue; |
| } |
| } |
| refreshPackages((Bundle[])installed.toArray(new Bundle[installed.size()])); |
| |
| Enumeration e = installed.elements(); |
| while (e.hasMoreElements()) { |
| bundle = (Bundle)e.nextElement(); |
| try { |
| bundle.start(); |
| } catch (BundleException ex) { |
| System.out.println("Error starting " + bundle.getLocation()); |
| ex.printStackTrace(); |
| } |
| } |
| // TODO remove this constant. At least set it to the max of 6 and the current value... |
| start.setStartLevel(6); |
| context.ungetService(reference); |
| System.out.println("Time (re)start the framework: " + (System.currentTimeMillis() - startTime)); |
| return (String[])ignored.toArray(new String[ignored.size()]); |
| } |
| |
| private static void refreshPackages(Bundle[] bundles) { |
| if (bundles.length == 0) |
| return; |
| ServiceReference packageAdminRef = context.getServiceReference(PackageAdmin.class.getName()); |
| PackageAdmin packageAdmin = null; |
| if (packageAdminRef != null) { |
| packageAdmin = (PackageAdmin)context.getService(packageAdminRef); |
| if (packageAdmin == null) |
| return; |
| } |
| // TODO this is such a hack it is silly. There are still cases for race conditions etc |
| // but this should allow for some progress... |
| final Semaphore semaphore = new Semaphore(0); |
| FrameworkListener listener = new FrameworkListener() { |
| public void frameworkEvent(FrameworkEvent event) { |
| if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) |
| semaphore.release(); |
| } |
| }; |
| context.addFrameworkListener(listener); |
| packageAdmin.refreshPackages(bundles); |
| semaphore.acquire(); |
| context.removeFrameworkListener(listener); |
| context.ungetService(packageAdminRef); |
| } |
| |
| /** |
| * Invokes the OSGi Console on another thread |
| * |
| * @param osgi The current OSGi instance for the console to attach to |
| * @param consoleArgs An String array containing commands from the command line |
| * for the console to execute |
| */ |
| private static void startConsole(OSGi osgi, String[] consoleArgs) { |
| try { |
| Class consoleClass = Class.forName(consoleClassName); |
| Class[] parameterTypes; |
| Object[] parameters; |
| if (consolePort.length() == 0) { |
| parameterTypes = new Class[] { OSGi.class, String[].class }; |
| parameters = new Object[] { osgi, consoleArgs }; |
| } else { |
| parameterTypes = new Class[] { OSGi.class, int.class, String[].class }; |
| parameters = new Object[] { osgi, new Integer(consolePort), consoleArgs }; |
| } |
| Constructor constructor = consoleClass.getConstructor(parameterTypes); |
| Object console = constructor.newInstance(parameters); |
| Thread t = new Thread(((Runnable) console), CONSOLE_NAME); |
| t.start(); |
| } catch (NumberFormatException nfe) { |
| System.err.println("Invalid console port: " + consolePort); |
| } catch (Exception ex) { |
| System.out.println("Failed to find/start: " + CONSOLE_NAME); |
| } |
| |
| } |
| |
| /** |
| * Creates and returns the adaptor |
| * |
| * @return a FrameworkAdaptor object |
| */ |
| private static FrameworkAdaptor createAdaptor() throws Exception { |
| Class adaptorClass = Class.forName(adaptorClassName); |
| Class[] constructorArgs = new Class[] { String[].class }; |
| Constructor constructor = adaptorClass.getConstructor(constructorArgs); |
| return (FrameworkAdaptor) constructor.newInstance(new Object[] { new String[0] }); |
| } |
| |
| private static String[] processCommandLine(String[] args) throws Exception { |
| EnvironmentInfo.allArgs = args; |
| int[] configArgs = new int[100]; |
| 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 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$ |
| System.getProperties().put("osgi.dev", ""); |
| found = true; |
| continue; |
| } |
| |
| // look for the console with no port. |
| if (args[i].equalsIgnoreCase(CONSOLE)) { |
| console = true; |
| found = true; |
| continue; |
| } |
| |
| 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)) { |
| console = true; |
| consolePort = arg; |
| found = true; |
| continue; |
| } |
| |
| // look for the configuraiton location . |
| if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) { |
| configLocation = arg; |
| continue; |
| } |
| |
| // look for the development mode and class path entries. |
| if (args[i - 1].equalsIgnoreCase(DEV)) { |
| System.getProperties().put("osgi.dev", arg); |
| found = true; |
| continue; |
| } |
| |
| // look for the data location for this instance. |
| if (args[i - 1].equalsIgnoreCase(DATA)) { |
| dataLocation = arg; |
| continue; |
| } |
| |
| // look for the debug mode and option file location. |
| if (args[i - 1].equalsIgnoreCase(DEBUG)) { |
| System.getProperties().put("osgi.debug", arg); |
| found = true; |
| continue; |
| } |
| |
| // look for the window system. |
| if (args[i - 1].equalsIgnoreCase(WS)) { |
| found = true; |
| System.getProperties().put("osgi.ws", arg); |
| } |
| |
| // look for the operating system |
| if (args[i - 1].equalsIgnoreCase(OS)) { |
| found = true; |
| System.getProperties().put("osgi.os", arg); |
| } |
| |
| // look for the system architecture |
| if (args[i - 1].equalsIgnoreCase(ARCH)) { |
| found = true; |
| System.getProperties().put("osgi.arch", arg); |
| } |
| |
| // look for the nationality/language |
| if (args[i - 1].equalsIgnoreCase(NL)) { |
| found = true; |
| System.getProperties().put("osgi.nl", arg); |
| } |
| |
| // 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) { |
| EnvironmentInfo.frameworkArgs = new String[0]; |
| EnvironmentInfo.appArgs = args; |
| return args; |
| } |
| EnvironmentInfo.appArgs = new String[args.length - configArgIndex]; |
| EnvironmentInfo.frameworkArgs = new String[configArgIndex]; |
| configArgIndex = 0; |
| int j = 0; |
| int k = 0; |
| for (int i = 0; i < args.length; i++) { |
| if (i == configArgs[configArgIndex]) { |
| EnvironmentInfo.frameworkArgs[k++] = args[i]; |
| configArgIndex++; |
| } else |
| EnvironmentInfo.appArgs[j++] = args[i]; |
| } |
| return EnvironmentInfo.appArgs; |
| } |
| |
| /** |
| * 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) { |
| if (prop == null || prop.trim().equals("")) //$NON-NLS-1$ |
| return new String[0]; |
| Vector list = new Vector(); |
| StringTokenizer tokens = new StringTokenizer(prop, ","); //$NON-NLS-1$ |
| while (tokens.hasMoreTokens()) { |
| String token = tokens.nextToken().trim(); |
| if (!token.equals("")) //$NON-NLS-1$ |
| list.addElement(token); |
| } |
| return list.isEmpty() ? new String[0] : (String[]) list.toArray(new String[list.size()]); |
| } |
| |
| private static String getSysPath() { |
| String result = System.getProperty("osgi.syspath"); |
| if (result != null) |
| return result; |
| |
| URL url = EclipseStarter.class.getProtectionDomain().getCodeSource().getLocation(); |
| result = url.getFile(); |
| if (result.endsWith("/")) |
| result = result.substring(0, result.length() - 1); |
| result = result.substring(0, result.lastIndexOf('/')); |
| result = result.substring(0, result.lastIndexOf('/')); |
| return result; |
| } |
| |
| private static void setInstanceLocation() { |
| File result = null; |
| String location = System.getProperty("osgi.instance.area"); |
| // if the instance location is not set, predict where the workspace will be and |
| // put the instance area inside the workspace meta area. |
| if (location == null) { |
| if (dataLocation == null) |
| result = new File(System.getProperty("user.dir"), "workspace");//$NON-NLS-1$ //$NON-NLS-2$ |
| else |
| result = new File(dataLocation); |
| result = new File(result, ".metadata/bundles"); |
| } else { |
| result = new File(location); |
| } |
| System.getProperties().put("osgi.instance.area", result.getAbsolutePath()); |
| System.getProperties().put("org.eclipse.osgi.framework.defaultadaptor.bundledir", result.getAbsolutePath()); |
| } |
| |
| private static void setConfigurationLocation() { |
| String location = System.getProperty("osgi.configuration.area"); |
| if (location != null) { |
| configLocation = location; |
| if (System.getProperty("osgi.manifest.cache") == null) |
| System.getProperties().put("osgi.manifest.cache", configLocation + "/manifests"); |
| return; |
| } |
| // -configuration was not specified so compute a configLocation based on the |
| // install location. If it is read/write then use it. Otherwise use the user.dir |
| if (configLocation == null) { |
| configLocation = getDefaultStateLocation() + "/.config"; |
| // TODO handle the case where the install location is read only. Find out |
| // how they do it in the update code and do the same here. |
| } else { |
| // if -configuration was specified, then interpret the config location from the |
| // value given. Allow for the specification of a config file (.cfg) or a dir. |
| try { |
| configLocation = new URL(configLocation).getFile(); |
| } catch (MalformedURLException e) { |
| // TODO do something in the error case |
| } |
| configLocation = configLocation.replace('\\', '/'); |
| int index = configLocation.lastIndexOf('/'); |
| if (configLocation.endsWith(".cfg") || configLocation.endsWith("/")) |
| configLocation = configLocation.substring(0, index); |
| } |
| System.getProperties().put("osgi.configuration.area", configLocation); |
| if (System.getProperty("osgi.manifest.cache") == null) { |
| System.getProperties().put("osgi.manifest.cache", configLocation + "/manifests"); |
| } |
| } |
| |
| private static boolean isInstalled(String location) { |
| Bundle[] installed = context.getBundles(); |
| for (int i = 0; i < installed.length; i++) { |
| Bundle bundle = installed[i]; |
| if (location.equalsIgnoreCase(bundle.getLocation())) |
| return true; |
| } |
| return false; |
| } |
| |
| private static String getDefaultStateLocation() { |
| // 1) We store the config state relative to the 'eclipse' directory if possible |
| // 2) If this directory is read-only |
| // we store the state in <user.home>/.eclipse/<application-id>_<version> where <user.home> |
| // is unique for each local user, and <application-id> is the one |
| // defined in .eclipseproduct marker file. If .eclipseproduct does not |
| // exist, use "eclipse" as the application-id. |
| |
| String installProperty = System.getProperty("eclipse.installURL"); |
| URL installURL = null; |
| try { |
| installURL = new URL(installProperty); |
| } catch (MalformedURLException e) { |
| // do nothgin here since it is basically impossible to get a bogus url |
| } |
| File installDir = new File(installURL.getFile()); |
| if ("file".equals(installURL.getProtocol()) && installDir.canWrite()) { //$NON-NLS-1$ |
| // if (DEBUG) |
| // debug("Using the installation directory."); //$NON-NLS-1$ |
| return installDir.getAbsolutePath(); |
| } |
| |
| // We can't write in the eclipse install dir so try for some place in the user's home dir |
| // if (DEBUG) |
| // debug("Using the user.home location."); //$NON-NLS-1$ |
| String appName = "." + ECLIPSE; //$NON-NLS-1$ |
| File eclipseProduct = new File(installDir, PRODUCT_SITE_MARKER ); |
| if (eclipseProduct.exists()) { |
| Properties props = new Properties(); |
| try { |
| props.load(new FileInputStream(eclipseProduct)); |
| String appId = props.getProperty(PRODUCT_SITE_ID); |
| if (appId == null || appId.trim().length() == 0) |
| appId = ECLIPSE; |
| String appVersion = props.getProperty(PRODUCT_SITE_VERSION); |
| if (appVersion == null || appVersion.trim().length() == 0) |
| appVersion = ""; //$NON-NLS-1$ |
| appName += File.separator + appId + "_" + appVersion; //$NON-NLS-1$ |
| } catch (IOException e) { |
| // Do nothing if we get an exception. We will default to a standard location |
| // in the user's home dir. |
| } |
| } |
| |
| String userHome = System.getProperty("user.home"); //$NON-NLS-1$ |
| File configDir = new File(userHome, appName); |
| configDir.mkdirs(); |
| return configDir.getAbsolutePath(); |
| } |
| |
| private static void initializeApplicationTracker() { |
| Filter filter = null; |
| try { |
| filter = context.createFilter("(&(objectClass=java.lang.Runnable)(eclipse.application=*))"); |
| } catch (InvalidSyntaxException e) { |
| // ignore this. It should never happen as we have tested the above format. |
| e.printStackTrace(); |
| } |
| applicationTracker = new ServiceTracker(context, filter, null); |
| applicationTracker.open(); |
| } |
| } |
| |
| |