Source code: org/eclipse/core/runtime/adaptor/EclipseStarter.java
1 /*******************************************************************************
2 * Copyright (c) 2003,2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.core.runtime.adaptor;
12
13 import java.io.*;
14 import java.lang.reflect.Constructor;
15 import java.net.*;
16 import java.util.*;
17 import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
18 import org.eclipse.osgi.framework.internal.core.OSGi;
19 import org.eclipse.osgi.framework.log.FrameworkLog;
20 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
21 import org.eclipse.osgi.framework.stats.StatsManager;
22 import org.eclipse.osgi.service.datalocation.Location;
23 import org.eclipse.osgi.service.resolver.*;
24 import org.eclipse.osgi.service.runnable.ParameterizedRunnable;
25 import org.osgi.framework.*;
26 import org.osgi.service.packageadmin.PackageAdmin;
27 import org.osgi.service.startlevel.StartLevel;
28 import org.osgi.util.tracker.ServiceTracker;
29
30 /**
31 * Special startup class for the Eclipse Platform. This class cannot be
32 * instantiated; all functionality is provided by static methods.
33 * <p>
34 * The Eclipse Platform makes heavy use of Java class loaders for loading
35 * plug-ins. Even the Eclispe Runtime itself and the OSGi framework need
36 * to be loaded by special class loaders. The upshot is that a
37 * client program (such as a Java main program, a servlet) cannot
38 * reference any part of Eclipse directly. Instead, a client must use this
39 * loader class to start the platform, invoking functionality defined
40 * in plug-ins, and shutting down the platform when done.
41 * </p>
42 * <p>Note that the fields on this class are not API. </p>
43 * @since 3.0
44 */
45 public class EclipseStarter {
46 private static FrameworkAdaptor adaptor;
47 private static BundleContext context;
48 private static ServiceTracker applicationTracker;
49 private static boolean initialize = false;
50 public static boolean debug = false;
51 private static boolean running = false;
52
53 // command line arguments
54 private static final String CLEAN = "-clean"; //$NON-NLS-1$
55 private static final String CONSOLE = "-console"; //$NON-NLS-1$
56 private static final String CONSOLE_LOG = "-consoleLog"; //$NON-NLS-1$
57 private static final String DEBUG = "-debug"; //$NON-NLS-1$
58 private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$
59 private static final String DEV = "-dev"; //$NON-NLS-1$
60 private static final String WS = "-ws"; //$NON-NLS-1$
61 private static final String OS = "-os"; //$NON-NLS-1$
62 private static final String ARCH = "-arch"; //$NON-NLS-1$
63 private static final String NL = "-nl"; //$NON-NLS-1$
64 private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$
65 private static final String USER = "-user"; //$NON-NLS-1$
66 // this is more of an Eclipse argument but this OSGi implementation stores its
67 // metadata alongside Eclipse's.
68 private static final String DATA = "-data"; //$NON-NLS-1$
69
70 // System properties
71 public static final String PROP_BUNDLES = "osgi.bundles"; //$NON-NLS-1$
72 public static final String PROP_BUNDLES_STARTLEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$
73 public static final String PROP_INITIAL_STARTLEVEL = "osgi.startLevel"; //$NON-NLS-1$
74 public static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$
75 public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$
76 public static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$
77 public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$
78 public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass"; //$NON-NLS-1$
79 public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$
80 public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$
81 public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$
82 public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$
83 public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$
84 public static final String PROP_ADAPTOR = "osgi.adaptor"; //$NON-NLS-1$
85 public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$
86 public static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$
87
88 public static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$
89 public static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$
90 public static final String PROP_CONSOLE_LOG = "eclipse.consoleLog"; //$NON-NLS-1$
91 private static final String PROP_VM = "eclipse.vm"; //$NON-NLS-1$
92 private static final String PROP_VMARGS = "eclipse.vmargs"; //$NON-NLS-1$
93 private static final String PROP_COMMANDS = "eclipse.commands"; //$NON-NLS-1$
94
95 private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$
96 private static final String FILE_PROTOCOL = "file"; //$NON-NLS-1$
97 private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$
98 private static final String REFERENCE_PROTOCOL = "reference"; //$NON-NLS-1$
99 private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$
100 /** string containing the classname of the adaptor to be used in this framework instance */
101 protected static final String DEFAULT_ADAPTOR_CLASS = "org.eclipse.core.runtime.adaptor.EclipseAdaptor"; //$NON-NLS-1$
102
103 // Console information
104 protected static final String DEFAULT_CONSOLE_CLASS = "org.eclipse.osgi.framework.internal.core.FrameworkConsole"; //$NON-NLS-1$
105 private static final String CONSOLE_NAME = "OSGi Console"; //$NON-NLS-1$
106
107 private static FrameworkLog log;
108
109 /**
110 * Launches the platform and runs a single application. The application is either identified
111 * in the given arguments (e.g., -application <app id>) or in the <code>eclipse.application</code>
112 * System property. This convenience method starts
113 * up the platform, runs the indicated application, and then shuts down the
114 * platform. The platform must not be running already.
115 *
116 * @param args the command line-style arguments used to configure the platform
117 * @param endSplashHandler the block of code to run to tear down the splash
118 * screen or <code>null</code> if no tear down is required
119 * @return the result of running the application
120 * @throws Exception if anything goes wrong
121 */
122 public static Object run(String[] args, Runnable endSplashHandler) throws Exception {
123 if (running)
124 throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ALREADY_RUNNING")); //$NON-NLS-1$
125 boolean startupFailed = true;
126 try {
127 startup(args, endSplashHandler);
128 startupFailed = false;
129 return run(null);
130 } catch (Throwable e) {
131 // ensure the splash screen is down
132 if (endSplashHandler != null)
133 endSplashHandler.run();
134 // may use startupFailed to understand where the error happened
135 FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, startupFailed ? EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_STARTUP_ERROR") : EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_APP_ERROR"), 1, e, null); //$NON-NLS-1$//$NON-NLS-2$
136 if (log != null) {
137 log.log(logEntry);
138 logUnresolvedBundles(context.getBundles());
139 } else
140 // TODO desperate measure - ideally, we should write this to disk (a la Main.log)
141 e.printStackTrace();
142 } finally {
143 try {
144 shutdown();
145 } catch (Throwable e) {
146 FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_SHUTDOWN_ERROR"), 1, e, null); //$NON-NLS-1$
147 if (log != null)
148 log.log(logEntry);
149 else
150 // TODO desperate measure - ideally, we should write this to disk (a la Main.log)
151 e.printStackTrace();
152 }
153 }
154 // we only get here if an error happened
155 System.getProperties().put(PROP_EXITCODE, "13"); //$NON-NLS-1$
156 System.getProperties().put(PROP_EXITDATA, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_CHECK_LOG", log.getFile().getPath())); //$NON-NLS-1$
157 return null;
158 }
159
160 /**
161 * Returns true if the platform is already running, false otherwise.
162 * @return whether or not the platform is already running
163 */
164 public static boolean isRunning() {
165 return running;
166 }
167
168 protected static FrameworkLog createFrameworkLog() {
169 FrameworkLog frameworkLog;
170 String logFileProp = System.getProperty(EclipseStarter.PROP_LOGFILE);
171 if (logFileProp != null) {
172 frameworkLog = new EclipseLog(new File(logFileProp));
173 } else {
174 Location location = LocationManager.getConfigurationLocation();
175 File configAreaDirectory = null;
176 if (location != null)
177 // TODO assumes the URL is a file: url
178 configAreaDirectory = new File(location.getURL().getFile());
179
180 if (configAreaDirectory != null) {
181 String logFileName = Long.toString(System.currentTimeMillis()) + EclipseAdaptor.F_LOG;
182 File logFile = new File(configAreaDirectory, logFileName);
183 System.getProperties().put(EclipseStarter.PROP_LOGFILE, logFile.getAbsolutePath());
184 frameworkLog = new EclipseLog(logFile);
185 } else
186 frameworkLog = new EclipseLog();
187 }
188 if ("true".equals(System.getProperty(EclipseStarter.PROP_CONSOLE_LOG))) //$NON-NLS-1$
189 frameworkLog.setConsoleLog(true);
190 return frameworkLog;
191 }
192
193 /**
194 * Starts the platform and sets it up to run a single application. The application is either identified
195 * in the given arguments (e.g., -application <app id>) or in the <code>eclipse.application</code>
196 * System property. The platform must not be running already.
197 * <p>
198 * The given runnable (if not <code>null</code>) is used to tear down the splash screen if required.
199 * </p>
200 * @param args the arguments passed to the application
201 * @throws Exception if anything goes wrong
202 */
203 public static void startup(String[] args, Runnable endSplashHandler) throws Exception {
204 if (running)
205 throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ALREADY_RUNNING")); //$NON-NLS-1$
206 long start = 0;
207 processCommandLine(args);
208 LocationManager.initializeLocations();
209 log = createFrameworkLog();
210 loadConfigurationInfo();
211 loadDefaultProperties();
212 finalizeProperties();
213 adaptor = createAdaptor();
214 ((EclipseAdaptor) adaptor).setLog(log);
215 OSGi osgi = new OSGi(adaptor);
216 osgi.launch();
217 String console = System.getProperty(PROP_CONSOLE);
218 if (console != null)
219 startConsole(osgi, new String[0], console);
220 context = osgi.getBundleContext();
221 publishSplashScreen(endSplashHandler);
222 Bundle[] startBundles = loadBasicBundles();
223 // set the framework start level to the ultimate value. This will actually start things
224 // running if they are persistently active.
225 setStartLevel(getStartLevel());
226 // they should all be active by this time
227 ensureBundlesActive(startBundles);
228 if (debug)
229 logUnresolvedBundles(context.getBundles());
230 running = true;
231 }
232
233 private static int getStartLevel() {
234 String level = System.getProperty(PROP_INITIAL_STARTLEVEL);
235 if (level != null)
236 try {
237 return Integer.parseInt(level);
238 } catch (NumberFormatException e) {
239 if (debug)
240 System.out.println("Start level = " + level + " parsed. Using hardcoded default: 6"); //$NON-NLS-1$ //$NON-NLS-2$
241 }
242 return 6; // hard coded default value for legacy purposes
243 }
244
245 /**
246 * Runs the applicaiton for which the platform was started. The platform
247 * must be running.
248 * <p>
249 * The given argument is passed to the application being run. If it is <code>null</code>
250 * then the command line arguments used in starting the platform, and not consumed
251 * by the platform code, are passed to the application as a <code>String[]</code>.
252 * </p>
253 * @param argument the argument passed to the application. May be <code>null</code>
254 * @return the result of running the application
255 * @throws Exception if anything goes wrong
256 */
257 public static Object run(Object argument) throws Exception {
258 if (!running)
259 throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_NOT_RUNNING")); //$NON-NLS-1$
260 // if we are just initializing, do not run the application just return.
261 if (initialize)
262 return new Integer(0);
263 initializeApplicationTracker();
264 ParameterizedRunnable application = (ParameterizedRunnable) applicationTracker.getService();
265 applicationTracker.close();
266 if (application == null)
267 throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_NO_APPLICATION")); //$NON-NLS-1$
268 if (debug) {
269 String timeString = System.getProperty("eclipse.startTime"); //$NON-NLS-1$
270 long time = timeString == null ? 0L : Long.parseLong(timeString);
271 System.out.println("Starting application: " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
272 }
273 return application.run(argument);
274 }
275
276 /**
277 * Shuts down the Platform. The state of the Platform is not automatically
278 * saved before shutting down.
279 * <p>
280 * On return, the Platform will no longer be running (but could be re-launched
281 * with another call to startup). If relaunching, care must be taken to reinitialize
282 * any System properties which the platform uses (e.g., osgi.instance.area) as
283 * some policies in the platform do not allow resetting of such properties on
284 * subsequent runs.
285 * </p><p>
286 * Any objects handed out by running Platform,
287 * including Platform runnables obtained via getRunnable, will be
288 * permanently invalid. The effects of attempting to invoke methods
289 * on invalid objects is undefined.
290 * </p>
291 * @throws Exception if anything goes wrong
292 */
293 public static void shutdown() throws Exception {
294 if (!running)
295 return;
296 stopSystemBundle();
297 }
298
299 private static void ensureBundlesActive(Bundle[] bundles) {
300 for (int i = 0; i < bundles.length; i++) {
301 if (bundles[i].getState() != Bundle.ACTIVE) {
302 String message = EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_ACTIVE", bundles[i]); //$NON-NLS-1$
303 throw new IllegalStateException(message);
304 }
305 }
306 }
307
308 private static void logUnresolvedBundles(Bundle[] bundles) {
309 State state = adaptor.getState();
310 FrameworkLog logService = adaptor.getFrameworkLog();
311 StateHelper stateHelper = adaptor.getPlatformAdmin().getStateHelper();
312 for (int i = 0; i < bundles.length; i++)
313 if (bundles[i].getState() == Bundle.INSTALLED) {
314 String generalMessage = EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED", bundles[i]); //$NON-NLS-1$
315 BundleDescription description = state.getBundle(bundles[i].getBundleId());
316 // for some reason, the state does not know about that bundle
317 if (description == null)
318 continue;
319 FrameworkLogEntry[] logChildren = null;
320 VersionConstraint[] unsatisfied = stateHelper.getUnsatisfiedConstraints(description);
321 if (unsatisfied.length > 0) {
322 // the bundle wasn't resolved due to some of its constraints were unsatisfiable
323 logChildren = new FrameworkLogEntry[unsatisfied.length];
324 for (int j = 0; j < unsatisfied.length; j++)
325 logChildren[j] = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.getResolutionFailureMessage(unsatisfied[j]), 0, null, null);
326 } else if (description.getSymbolicName() != null) {
327 BundleDescription[] homonyms = state.getBundles(description.getSymbolicName());
328 for (int j = 0; j < homonyms.length; j++)
329 if (homonyms[j].isResolved()) {
330 logChildren = new FrameworkLogEntry[1];
331 logChildren[0] = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_CONSOLE_OTHER_VERSION", homonyms[j].getLocation()), 0, null, null); //$NON-NLS-1$
332 }
333 }
334
335 logService.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, generalMessage, 0, null, logChildren));
336 }
337 }
338
339 private static void publishSplashScreen(final Runnable endSplashHandler) {
340 // InternalPlatform now how to retrieve this later
341 Dictionary properties = new Hashtable();
342 properties.put("name", "splashscreen"); //$NON-NLS-1$ //$NON-NLS-2$
343 Runnable handler = new Runnable() {
344 public void run() {
345 StatsManager.doneBooting();
346 endSplashHandler.run();
347 }
348 };
349 context.registerService(Runnable.class.getName(), handler, properties);
350 }
351
352 private static URL searchForBundle(String name, String parent) throws MalformedURLException {
353 URL url = null;
354 File fileLocation = null;
355 boolean reference = false;
356 try {
357 URL child = new URL(name);
358 url = new URL(new File(parent).toURL(), name);
359 } catch (MalformedURLException e) {
360 // TODO this is legacy support for non-URL names. It should be removed eventually.
361 // if name was not a URL then construct one.
362 // Assume it should be a reference and htat it is relative. This support need not
363 // be robust as it is temporary..
364 File child = new File(name);
365 fileLocation = child.isAbsolute() ? child : new File(parent, name);
366 url = new URL(REFERENCE_PROTOCOL, null, fileLocation.toURL().toExternalForm());
367 reference = true;
368 }
369 // if the name was a URL then see if it is relative. If so, insert syspath.
370 if (!reference) {
371 URL baseURL = url;
372 // if it is a reference URL then strip off the reference: and set base to the file:...
373 if (url.getProtocol().equals(REFERENCE_PROTOCOL)) {
374 reference = true;
375 String baseSpec = url.getFile();
376 if (baseSpec.startsWith(FILE_SCHEME)) {
377 File child = new File(baseSpec.substring(5));
378 baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL();
379 } else
380 baseURL = new URL(baseSpec);
381 }
382
383 fileLocation = new File(baseURL.getFile());
384 // if the location is relative, prefix it with the parent
385 if (!fileLocation.isAbsolute())
386 fileLocation = new File(parent, fileLocation.toString());
387 }
388 // If the result is a reference then search for the real result and
389 // reconstruct the answer.
390 if (reference) {
391 String result = searchFor(fileLocation.getName(), new File(fileLocation.getParent()).getAbsolutePath());
392 if (result != null)
393 url = new URL(REFERENCE_PROTOCOL, null, FILE_SCHEME + result);
394 else
395 return null;
396 }
397
398 // finally we have something worth trying
399 try {
400 URLConnection result = url.openConnection();
401 result.connect();
402 return url;
403 } catch (IOException e) {
404 // int i = location.lastIndexOf('_');
405 // return i == -1? location : location.substring(0, i);
406 return null;
407 }
408 }
409
410 /*
411 * Ensure all basic bundles are installed, resolved and scheduled to start. Returns an array containing
412 * all basic bundles that are marked to start.
413 */
414 private static Bundle[] loadBasicBundles() throws IOException {
415 long startTime = System.currentTimeMillis();
416 String[] installEntries = getArrayFromList(System.getProperty(PROP_BUNDLES), ","); //$NON-NLS-1$
417 // get the initial bundle list from the installEntries
418 InitialBundle[] initialBundles = getInitialBundles(installEntries);
419 // get the list of currently installed initial bundles from the framework
420 Bundle[] curInitBundles = getCurrentInitialBundles();
421
422 // uninstall any of the currently installed bundles that do not exist in the
423 // initial bundle list from installEntries.
424 boolean refresh = uninstallBundles(curInitBundles, initialBundles);
425
426 // install the initialBundles that are not already installed.
427 ArrayList newInitBundles = new ArrayList(installEntries.length);
428 ArrayList startBundles = new ArrayList(installEntries.length);
429 refresh |= installBundles(initialBundles, curInitBundles, startBundles, newInitBundles);
430
431 // If we installed/uninstalled something, force a refresh of all installed bundles
432 if (refresh) {
433 Bundle[] installedBundles = (Bundle[]) newInitBundles.toArray(new Bundle[newInitBundles.size()]);
434 refreshPackages(installedBundles);
435 }
436
437 // schedule all basic bundles to be started
438 Bundle[] startInitBundles = (Bundle[]) startBundles.toArray(new Bundle[startBundles.size()]);
439 startBundles(startInitBundles);
440
441 if (debug)
442 System.out.println("Time to load bundles: " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$
443 return startInitBundles;
444 }
445
446 private static InitialBundle[] getInitialBundles(String[] installEntries) throws MalformedURLException {
447 ArrayList result = new ArrayList(installEntries.length);
448 int defaultStartLevel = Integer.parseInt(System.getProperty(PROP_BUNDLES_STARTLEVEL));
449 String syspath = getSysPath();
450 for (int i = 0; i < installEntries.length; i++) {
451 String name = installEntries[i];
452 int level = defaultStartLevel;
453 boolean start = false;
454 int index = name.indexOf('@');
455 if (index >= 0) {
456 String[] attributes = getArrayFromList(name.substring(index + 1, name.length()), ":"); //$NON-NLS-1$
457 name = name.substring(0, index);
458 for (int j = 0; j < attributes.length; j++) {
459 String attribute = attributes[j];
460 if (attribute.equals("start")) //$NON-NLS-1$
461 start = true;
462 else
463 level = Integer.parseInt(attribute);
464 }
465 }
466 URL location = searchForBundle(name, syspath);
467 if (location == null) {
468 FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_BUNDLE_NOT_FOUND", installEntries[i]), 0, null, null); //$NON-NLS-1$
469 log.log(entry);
470 // skip this entry
471 continue;
472 }
473 String locationString = INITIAL_LOCATION + location.toExternalForm();
474 result.add(new InitialBundle(locationString, location, level, start));
475 }
476 return (InitialBundle[]) result.toArray(new InitialBundle[result.size()]);
477 }
478
479 private static void refreshPackages(Bundle[] bundles) {
480 if (bundles.length == 0)
481 return;
482 ServiceReference packageAdminRef = context.getServiceReference(PackageAdmin.class.getName());
483 PackageAdmin packageAdmin = null;
484 if (packageAdminRef != null) {
485 packageAdmin = (PackageAdmin) context.getService(packageAdminRef);
486 if (packageAdmin == null)
487 return;
488 }
489 // TODO this is such a hack it is silly. There are still cases for race conditions etc
490 // but this should allow for some progress...
491 final Semaphore semaphore = new Semaphore(0);
492 FrameworkListener listener = new FrameworkListener() {
493 public void frameworkEvent(FrameworkEvent event) {
494 if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED)
495 semaphore.release();
496 }
497 };
498 context.addFrameworkListener(listener);
499 packageAdmin.refreshPackages(bundles);
500 semaphore.acquire();
501 context.removeFrameworkListener(listener);
502 context.ungetService(packageAdminRef);
503 }
504
505 /**
506 * Invokes the OSGi Console on another thread
507 *
508 * @param osgi The current OSGi instance for the console to attach to
509 * @param consoleArgs An String array containing commands from the command line
510 * for the console to execute
511 * @param consolePort the port on which to run the console. Empty string implies the default port.
512 */
513 private static void startConsole(OSGi osgi, String[] consoleArgs, String consolePort) {
514 try {
515 String consoleClassName = System.getProperty(PROP_CONSOLE_CLASS, DEFAULT_CONSOLE_CLASS);
516 Class consoleClass = Class.forName(consoleClassName);
517 Class[] parameterTypes;
518 Object[] parameters;
519 if (consolePort.length() == 0) {
520 parameterTypes = new Class[] {OSGi.class, String[].class};
521 parameters = new Object[] {osgi, consoleArgs};
522 } else {
523 parameterTypes = new Class[] {OSGi.class, int.class, String[].class};
524 parameters = new Object[] {osgi, new Integer(consolePort), consoleArgs};
525 }
526 Constructor constructor = consoleClass.getConstructor(parameterTypes);
527 Object console = constructor.newInstance(parameters);
528 Thread t = new Thread(((Runnable) console), CONSOLE_NAME);
529 t.start();
530 } catch (NumberFormatException nfe) {
531 // TODO log or something other than write on System.err
532 System.err.println(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_INVALID_PORT", consolePort)); //$NON-NLS-1$
533 } catch (Exception ex) {
534 System.out.println(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_FIND", CONSOLE_NAME)); //$NON-NLS-1$
535 }
536
537 }
538
539 /**
540 * Creates and returns the adaptor
541 *
542 * @return a FrameworkAdaptor object
543 */
544 private static FrameworkAdaptor createAdaptor() throws Exception {
545 String adaptorClassName = System.getProperty(PROP_ADAPTOR, DEFAULT_ADAPTOR_CLASS);
546 Class adaptorClass = Class.forName(adaptorClassName);
547 Class[] constructorArgs = new Class[] {String[].class};
548 Constructor constructor = adaptorClass.getConstructor(constructorArgs);
549 return (FrameworkAdaptor) constructor.newInstance(new Object[] {new String[0]});
550 }
551
552 private static String[] processCommandLine(String[] args) throws Exception {
553 EnvironmentInfo.allArgs = args;
554 if (args.length == 0) {
555 EnvironmentInfo.frameworkArgs = args;
556 EnvironmentInfo.appArgs = args;
557 return args;
558 }
559 int[] configArgs = new int[args.length];
560 configArgs[0] = -1; // need to initialize the first element to something that could not be an index.
561 int configArgIndex = 0;
562 for (int i = 0; i < args.length; i++) {
563 boolean found = false;
564 // check for args without parameters (i.e., a flag arg)
565
566 // check if debug should be enabled for the entire platform
567 // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -),
568 // simply enable debug. Otherwise, assume that that the following arg is
569 // actually the filename of an options file. This will be processed below.
570 if (args[i].equalsIgnoreCase(DEBUG) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
571 System.getProperties().put(PROP_DEBUG, ""); //$NON-NLS-1$
572 debug = true;
573 found = true;
574 }
575
576 // check if development mode should be enabled for the entire platform
577 // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -),
578 // simply enable development mode. Otherwise, assume that that the following arg is
579 // actually some additional development time class path entries. This will be processed below.
580 if (args[i].equalsIgnoreCase(DEV) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
581 System.getProperties().put(PROP_DEV, ""); //$NON-NLS-1$
582 found = true;
583 }
584
585 // look for the initialization arg
586 if (args[i].equalsIgnoreCase(INITIALIZE)) {
587 initialize = true;
588 found = true;
589 }
590
591 // look for the clean flag.
592 if (args[i].equalsIgnoreCase(CLEAN)) {
593 System.getProperties().put(PROP_CLEAN, "true"); //$NON-NLS-1$
594 found = true;
595 }
596
597 // look for the consoleLog flag
598 if (args[i].equalsIgnoreCase(CONSOLE_LOG)) {
599 System.getProperties().put(PROP_CONSOLE_LOG, "true"); //$NON-NLS-1$
600 found = true;
601 }
602
603 // look for the console with no port.
604 if (args[i].equalsIgnoreCase(CONSOLE) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
605 System.getProperties().put(PROP_CONSOLE, ""); //$NON-NLS-1$
606 found = true;
607 }
608
609 if (found) {
610 configArgs[configArgIndex++] = i;
611 continue;
612 }
613 // check for args with parameters. If we are at the last argument or if the next one
614 // has a '-' as the first character, then we can't have an arg with a parm so continue.
615 if (i == args.length - 1 || args[i + 1].startsWith("-")) { //$NON-NLS-1$
616 continue;
617 }
618 String arg = args[++i];
619
620 // look for the console and port.
621 if (args[i - 1].equalsIgnoreCase(CONSOLE)) {
622 System.getProperties().put(PROP_CONSOLE, arg);
623 found = true;
624 }
625
626 // look for the configuration location .
627 if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) {
628 System.getProperties().put(LocationManager.PROP_CONFIG_AREA, arg);
629 found = true;
630 }
631
632 // look for the data location for this instance.
633 if (args[i - 1].equalsIgnoreCase(DATA)) {
634 System.getProperties().put(LocationManager.PROP_INSTANCE_AREA, arg);
635 found = true;
636 }
637
638 // look for the user location for this instance.
639 if (args[i - 1].equalsIgnoreCase(USER)) {
640 System.getProperties().put(LocationManager.PROP_USER_AREA, arg);
641 found = true;
642 }
643
644 // look for the development mode and class path entries.
645 if (args[i - 1].equalsIgnoreCase(DEV)) {
646 System.getProperties().put(PROP_DEV, arg);
647 found = true;
648 }
649
650 // look for the debug mode and option file location.
651 if (args[i - 1].equalsIgnoreCase(DEBUG)) {
652 System.getProperties().put(PROP_DEBUG, arg);
653 debug = true;
654 found = true;
655 }
656
657 // look for the window system.
658 if (args[i - 1].equalsIgnoreCase(WS)) {
659 System.getProperties().put(PROP_WS, arg);
660 found = true;
661 }
662
663 // look for the operating system
664 if (args[i - 1].equalsIgnoreCase(OS)) {
665 System.getProperties().put(PROP_OS, arg);
666 found = true;
667 }
668
669 // look for the system architecture
670 if (args[i - 1].equalsIgnoreCase(ARCH)) {
671 System.getProperties().put(PROP_ARCH, arg);
672 found = true;
673 }
674
675 // look for the nationality/language
676 if (args[i - 1].equalsIgnoreCase(NL)) {
677 System.getProperties().put(PROP_NL, arg);
678 found = true;
679 }
680 // done checking for args. Remember where an arg was found
681 if (found) {
682 configArgs[configArgIndex++] = i - 1;
683 configArgs[configArgIndex++] = i;
684 }
685 }
686
687 // remove all the arguments consumed by this argument parsing
688 if (configArgIndex == 0) {
689 EnvironmentInfo.frameworkArgs = new String[0];
690 EnvironmentInfo.appArgs = args;
691 return args;
692 }
693 EnvironmentInfo.appArgs = new String[args.length - configArgIndex];
694 EnvironmentInfo.frameworkArgs = new String[configArgIndex];
695 configArgIndex = 0;
696 int j = 0;
697 int k = 0;
698 for (int i = 0; i < args.length; i++) {
699 if (i == configArgs[configArgIndex]) {
700 EnvironmentInfo.frameworkArgs[k++] = args[i];
701 configArgIndex++;
702 } else
703 EnvironmentInfo.appArgs[j++] = args[i];
704 }
705 return EnvironmentInfo.appArgs;
706 }
707
708 /**
709 * Returns the result of converting a list of comma-separated tokens into an array
710 *
711 * @return the array of string tokens
712 * @param prop the initial comma-separated string
713 */
714 private static String[] getArrayFromList(String prop, String separator) {
715 if (prop == null || prop.trim().equals("")) //$NON-NLS-1$
716 return new String[0];
717 Vector list = new Vector();
718 StringTokenizer tokens = new StringTokenizer(prop, separator); //$NON-NLS-1$
719 while (tokens.hasMoreTokens()) {
720 String token = tokens.nextToken().trim();
721 if (!token.equals("")) //$NON-NLS-1$
722 list.addElement(token);
723 }
724 return list.isEmpty() ? new String[0] : (String[]) list.toArray(new String[list.size()]);
725 }
726
727 protected static String getSysPath() {
728 String result = System.getProperty(PROP_SYSPATH);
729 if (result != null)
730 return result;
731
732 URL url = EclipseStarter.class.getProtectionDomain().getCodeSource().getLocation();
733 result = url.getFile();
734 if (result.endsWith("/")) //$NON-NLS-1$
735 result = result.substring(0, result.length() - 1);
736 result = result.substring(0, result.lastIndexOf('/'));
737 result = result.substring(0, result.lastIndexOf('/'));
738 if (Character.isUpperCase(result.charAt(0))) {
739 char[] chars = result.toCharArray();
740 chars[0] = Character.toLowerCase(chars[0]);
741 result = new String(chars);
742 }
743 return result;
744 }
745
746 private static Bundle[] getCurrentInitialBundles() {
747 Bundle[] installed = context.getBundles();
748 ArrayList initial = new ArrayList();
749 for (int i = 0; i < installed.length; i++) {
750 Bundle bundle = installed[i];
751 if (bundle.getLocation().startsWith(INITIAL_LOCATION))
752 initial.add(bundle);
753 }
754 return (Bundle[]) initial.toArray(new Bundle[initial.size()]);
755 }
756
757 private static Bundle getBundleByLocation(String location, Bundle[] bundles) {
758 for (int i = 0; i < bundles.length; i++) {
759 Bundle bundle = bundles[i];
760 if (location.equalsIgnoreCase(bundle.getLocation()))
761 return bundle;
762 }
763 return null;
764 }
765
766 private static boolean uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles) {
767 boolean uninstalledBundle = false;
768 for (int i = 0; i < curInitBundles.length; i++) {
769 boolean found = false;
770 for (int j = 0; j < newInitBundles.length; j++) {
771 if (curInitBundles[i].getLocation().equalsIgnoreCase(newInitBundles[j].locationString)) {
772 found = true;
773 break;
774 }
775 }
776 if (!found)
777 try {
778 curInitBundles[i].uninstall();
779 uninstalledBundle = true;
780 } catch (BundleException e) {
781 FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_UNINSTALL", curInitBundles[i].getLocation()), 0, e, null); //$NON-NLS-1$
782 log.log(entry);
783 }
784 }
785 return uninstalledBundle;
786 }
787
788 private static boolean installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, ArrayList startBundles, ArrayList newInitBundles) {
789 boolean installed = false;
790 ServiceReference reference = context.getServiceReference(StartLevel.class.getName());
791 StartLevel startService = null;
792 if (reference != null)
793 startService = (StartLevel) context.getService(reference);
794 for (int i = 0; i < initialBundles.length; i++) {
795 Bundle osgiBundle = getBundleByLocation(initialBundles[i].locationString, curInitBundles);
796 try {
797 // don't need to install if it is already installed
798 if (osgiBundle == null) {
799 InputStream in = initialBundles[i].location.openStream();
800 osgiBundle = context.installBundle(initialBundles[i].locationString, in);
801 installed = true;
802 if (initialBundles[i].level >= 0 && startService != null)
803 startService.setBundleStartLevel(osgiBundle, initialBundles[i].level);
804 }
805 if (initialBundles[i].start)
806 startBundles.add(osgiBundle);
807 newInitBundles.add(osgiBundle);
808 } catch (BundleException e) {
809 FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_INSTALL", initialBundles[i].location), 0, e, null); //$NON-NLS-1$
810 log.log(entry);
811 } catch (IOException e) {
812 FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_INSTALL", initialBundles[i].location), 0, e, null); //$NON-NLS-1$
813 log.log(entry);
814 }
815 }
816 context.ungetService(reference);
817 return installed;
818 }
819
820 private static void startBundles(Bundle[] bundles) {
821 for (int i = 0; i < bundles.length; i++) {
822 Bundle bundle = bundles[i];
823 if (bundle.getState() == Bundle.INSTALLED)
824 throw new IllegalStateException(EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED", bundle.getLocation())); //$NON-NLS-1$
825 try {
826 bundle.start();
827 } catch (BundleException e) {
828 FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, EclipseAdaptorMsg.formatter.getString("ECLIPSE_STARTUP_FAILED_START", bundle.getLocation()), 0, e, null); //$NON-NLS-1$
829 log.log(entry);
830 }
831 }
832 }
833
834 private static void initializeApplicationTracker() {
835 Filter filter = null;
836 try {
837 String appClass = ParameterizedRunnable.class.getName();
838 filter = context.createFilter("(&(objectClass=" + appClass + ")(eclipse.application=*))"); //$NON-NLS-1$ //$NON-NLS-2$
839 } catch (InvalidSyntaxException e) {
840 // ignore this. It should never happen as we have tested the above format.
841 }
842 applicationTracker = new ServiceTracker(context, filter, null);
843 applicationTracker.open();
844 }
845
846 private static void loadConfigurationInfo() {
847 Location configArea = LocationManager.getConfigurationLocation();
848 if (configArea == null)
849 return;
850
851 URL location = null;
852 try {
853 location = new URL(configArea.getURL().toExternalForm() + LocationManager.CONFIG_FILE);
854 } catch (MalformedURLException e) {
855 // its ok. Thie should never happen
856 }
857 mergeProperties(System.getProperties(), loadProperties(location));
858 }
859
860 private static void loadDefaultProperties() {
861 URL codeLocation = EclipseStarter.class.getProtectionDomain().getCodeSource().getLocation();
862 if (codeLocation == null)
863 return;
864 String location = codeLocation.getFile();
865 if (location.endsWith("/")) //$NON-NLS-1$
866 location = location.substring(0, location.length() - 1);
867 int i = location.lastIndexOf('/');
868 location = location.substring(0, i + 1) + LocationManager.ECLIPSE_PROPERTIES;
869 URL result = null;
870 try {
871 result = new File(location).toURL();
872 } catch (MalformedURLException e) {
873 // its ok. Thie should never happen
874 }
875 mergeProperties(System.getProperties(), loadProperties(result));
876 }
877
878 private static Properties loadProperties(URL location) {
879 Properties result = new Properties();
880 if (location == null)
881 return result;
882 try {
883 InputStream in = location.openStream();
884 try {
885 result.load(in);
886 } finally {
887 in.close();
888 }
889 } catch (IOException e) {
890 // its ok if there is no file. We'll just use the defaults for everything
891 // TODO but it might be nice to log something with gentle wording (i.e., it is not an error)
892 }
893 return result;
894 }
895
896 private static void mergeProperties(Properties destination, Properties source) {
897 for (Enumeration e = source.keys(); e.hasMoreElements();) {
898 String key = (String) e.nextElement();
899 String value = source.getProperty(key);
900 if (destination.getProperty(key) == null)
901 destination.put(key, value);
902 }
903 }
904
905 private static void stopSystemBundle() throws BundleException {
906 if (context == null || !running)
907 return;
908 Bundle systemBundle = context.getBundle(0);
909 if (systemBundle.getState() == Bundle.ACTIVE) {
910 final Semaphore semaphore = new Semaphore(0);
911 FrameworkListener listener = new FrameworkListener() {
912 public void frameworkEvent(FrameworkEvent event) {
913 if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED)
914 semaphore.release();
915 }
916
917 };
918 context.addFrameworkListener(listener);
919 systemBundle.stop();
920 semaphore.acquire();
921 context.removeFrameworkListener(listener);
922 }
923 context = null;
924 applicationTracker = null;
925 running = false;
926 }
927
928 private static void setStartLevel(final int value) {
929 ServiceTracker tracker = new ServiceTracker(context, StartLevel.class.getName(), null);
930 tracker.open();
931 final StartLevel startLevel = (StartLevel) tracker.getService();
932 final Semaphore semaphore = new Semaphore(0);
933 FrameworkListener listener = new FrameworkListener() {
934 public void frameworkEvent(FrameworkEvent event) {
935 if (event.getType() == FrameworkEvent.STARTLEVEL_CHANGED && startLevel.getStartLevel() == value)
936 semaphore.release();
937 }
938 };
939 context.addFrameworkListener(listener);
940 startLevel.setStartLevel(value);
941 semaphore.acquire();
942 context.removeFrameworkListener(listener);
943 tracker.close();
944 }
945
946 /**
947 * Searches for the given target directory starting in the "plugins" subdirectory
948 * of the given location. If one is found then this location is returned;
949 * otherwise an exception is thrown.
950 *
951 * @return the location where target directory was found
952 * @param start the location to begin searching
953 */
954 private static String searchFor(final String target, String start) {
955 String[] candidates = new File(start).list();
956 if (candidates == null)
957 return null;
958 String result = null;
959 Object maxVersion = null;
960 for (int i = 0; i < candidates.length; i++) {
961 File candidate = new File(start, candidates[i]);
962 if (!candidate.getName().equals(target) && !candidate.getName().startsWith(target + "_")) //$NON-NLS-1$
963 continue;
964 String name = candidate.getName();
965 String version = ""; //$NON-NLS-1$ // Note: directory with version suffix is always > than directory without version suffix
966 int index = name.indexOf('_');
967 if (index != -1)
968 version = name.substring(index + 1);
969 Object currentVersion = getVersionElements(version);
970 if (maxVersion == null) {
971 result = candidate.getAbsolutePath();
972 maxVersion = currentVersion;
973 } else {
974 if (compareVersion((Object[]) maxVersion, (Object[]) currentVersion) < 0) {
975 result = candidate.getAbsolutePath();
976 maxVersion = currentVersion;
977 }
978 }
979 }
980 if (result == null)
981 return null;
982 return result.replace(File.separatorChar, '/') + "/"; //$NON-NLS-1$
983 }
984
985 /**
986 * Do a quick parse of version identifier so its elements can be correctly compared.
987 * If we are unable to parse the full version, remaining elements are initialized
988 * with suitable defaults.
989 * @return an array of size 4; first three elements are of type Integer (representing
990 * major, minor and service) and the fourth element is of type String (representing
991 * qualifier). Note, that returning anything else will cause exceptions in the caller.
992 */
993 private static Object[] getVersionElements(String version) {
994 Object[] result = {new Integer(0), new Integer(0), new Integer(0), ""}; //$NON-NLS-1$
995 StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$
996 String token;
997 int i = 0;
998 while (t.hasMoreTokens() && i < 4) {
999 token = t.nextToken();
1000 if (i < 3) {
1001 // major, minor or service ... numeric values
1002 try {
1003 result[i++] = new Integer(token);
1004 } catch (Exception e) {
1005 // invalid number format - use default numbers (0) for the rest
1006 break;
1007 }
1008 } else {
1009 // qualifier ... string value
1010 result[i++] = token;
1011 }
1012 }
1013 return result;
1014 }
1015
1016 /**
1017 * Compares version strings.
1018 * @return result of comparison, as integer;
1019 * <code><0</code> if left < right;
1020 * <code>0</code> if left == right;
1021 * <code>>0</code> if left > right;
1022 */
1023 private static int compareVersion(Object[] left, Object[] right) {
1024 int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major
1025 if (result != 0)
1026 return result;
1027
1028 result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor
1029 if (result != 0)
1030 return result;
1031
1032 result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service
1033 if (result != 0)
1034 return result;
1035
1036 return ((String) left[3]).compareTo((String) right[3]); // compare qualifier
1037 }
1038
1039 private static String buildCommandLine(String arg, String value) {
1040 StringBuffer result = new StringBuffer(300);
1041 String entry = System.getProperty(PROP_VM);
1042 if (entry == null)
1043 return null;
1044 result.append(entry);
1045 result.append('\n');
1046 // append the vmargs and commands. Assume that these already end in \n
1047 entry = System.getProperty(PROP_VMARGS);
1048 if (entry != null)
1049 result.append(entry);
1050 entry = System.getProperty(PROP_COMMANDS);
1051 if (entry != null)
1052 result.append(entry);
1053 String commandLine = result.toString();
1054 int i = commandLine.indexOf(arg + "\n"); //$NON-NLS-1$
1055 if (i == 0)
1056 commandLine += arg + "\n" + value + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
1057 else {
1058 i += arg.length() + 1;
1059 String left = commandLine.substring(0, i);
1060 int j = commandLine.indexOf('\n', i);
1061 String right = commandLine.substring(j);
1062 commandLine = left + value + right;
1063 }
1064 return commandLine;
1065 }
1066
1067 private static void finalizeProperties() {
1068 // if check config is unknown and we are in dev mode,
1069 if (System.getProperty(PROP_DEV) != null && System.getProperty(PROP_CHECK_CONFIG) == null)
1070 System.getProperties().put(PROP_CHECK_CONFIG, "true"); //$NON-NLS-1$
1071 }
1072
1073 private static class InitialBundle {
1074 public final String locationString;
1075 public final URL location;
1076 public final int level;
1077 public final boolean start;
1078
1079 InitialBundle(String locationString, URL location, int level, boolean start) {
1080 this.locationString = locationString;
1081 this.location = location;
1082 this.level = level;
1083 this.start = start;
1084 }
1085 }
1086}