/******************************************************************************* * Copyright (c) 2005, 2014 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.core.runtime.internal.adaptor; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.adaptor.EclipseStarter; import org.eclipse.osgi.framework.log.FrameworkLog; import org.eclipse.osgi.framework.log.FrameworkLogEntry; 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.messages.Msg; import org.eclipse.osgi.service.runnable.*; import org.osgi.framework.*; import org.osgi.framework.launch.Framework; public class EclipseAppLauncher implements ApplicationLauncher { volatile private ParameterizedRunnable runnable = null; private Object appContext = null; private final java.util.concurrent.Semaphore runningLock = new java.util.concurrent.Semaphore(1); private final java.util.concurrent.Semaphore waitForAppLock = new java.util.concurrent.Semaphore(0); private final BundleContext context; private boolean relaunch = false; private final boolean failOnNoDefault; private final FrameworkLog log; private final EquinoxConfiguration equinoxConfig; public EclipseAppLauncher(BundleContext context, boolean relaunch, boolean failOnNoDefault, FrameworkLog log, EquinoxConfiguration equinoxConfig) { this.context = context; this.relaunch = relaunch; this.failOnNoDefault = failOnNoDefault; this.log = log; this.equinoxConfig = equinoxConfig; findRunnableService(); } /* * Used for backwards compatibility with < 3.2 runtime */ private void findRunnableService() { // look for a ParameterizedRunnable registered as a service by runtimes (3.0, 3.1) String appClass = ParameterizedRunnable.class.getName(); ServiceReference<?>[] runRefs = null; try { runRefs = context.getServiceReferences(ParameterizedRunnable.class.getName(), "(&(objectClass=" + appClass + ")(eclipse.application=*))"); //$NON-NLS-1$//$NON-NLS-2$ } catch (InvalidSyntaxException e) { // ignore this. It should never happen as we have tested the above format. } if (runRefs != null && runRefs.length > 0) { // found the service use it as the application. runnable = (ParameterizedRunnable) context.getService(runRefs[0]); // we will never be able to relaunch with a pre 3.2 runtime relaunch = false; waitForAppLock.release(); } } /* * Starts this application launcher on the current thread. This method * should be called by the main thread to ensure that applications are * launched in the main thread. */ public Object start(Object defaultContext) throws Exception { // here we assume that launch has been called by runtime before we started // TODO this may be a bad assumption but it works for now because we register the app launcher as a service and runtime synchronously calls launch on the service if (failOnNoDefault && runnable == null) throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ERROR_NO_APPLICATION); Object result = null; boolean doRelaunch; Bundle b = context.getBundle(); do { try { if (relaunch) { // need a thread to kick the main thread when the framework stops final Thread mainThread = Thread.currentThread(); final BundleContext mainContext = context; new Thread(new Runnable() { @Override public void run() { Framework framework = (Framework) mainContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION); try { framework.waitForStop(0); // framework is done; tell the main thread to stop mainThread.interrupt(); } catch (InterruptedException e) { Thread.interrupted(); // just exiting now } } }, "Framework watcher").start(); //$NON-NLS-1$ } result = runApplication(defaultContext); } catch (Exception e) { if (!relaunch || (b.getState() & Bundle.ACTIVE) == 0) throw e; if (log != null) log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, Msg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null)); } doRelaunch = (relaunch && (b.getState() & Bundle.ACTIVE) != 0); } while (doRelaunch); return result; } /* * Waits for an application to be launched and the runs the application on the * current thread (main). */ private Object runApplication(Object defaultContext) throws Exception { try { // wait for an application to be launched. waitForAppLock.acquire(); // an application is ready; acquire the running lock. // this must happen after we have acquired an application (by acquiring waitForAppLock above). runningLock.acquire(); if (EclipseStarter.debug) { String timeString = equinoxConfig.getConfiguration("eclipse.startTime"); //$NON-NLS-1$ long time = timeString == null ? 0L : Long.parseLong(timeString); Debug.println("Starting application: " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ } try { // run the actual application on the current thread (main). return runnable.run(appContext != null ? appContext : defaultContext); } finally { // free the runnable application and release the lock to allow another app to be launched. runnable = null; appContext = null; runningLock.release(); } } catch (InterruptedException e) { // ignore; but mark interrupted for others Thread.interrupted(); } return null; } public void launch(ParameterizedRunnable app, Object applicationContext) { waitForAppLock.tryAcquire(); // clear out any pending apps notifications if (!runningLock.tryAcquire()) // check to see if an application is currently running throw new IllegalStateException("An application is aready running."); //$NON-NLS-1$ this.runnable = app; this.appContext = applicationContext; waitForAppLock.release(); // notify the main thread to launch an application. runningLock.release(); // release the running lock } public void shutdown() { // this method will aquire and keep the runningLock to prevent // all future application launches. if (runningLock.tryAcquire()) return; // no application is currently running. ParameterizedRunnable currentRunnable = runnable; if (currentRunnable instanceof ApplicationRunnable) { ((ApplicationRunnable) currentRunnable).stop(); try { runningLock.tryAcquire(1, TimeUnit.MINUTES); // timeout after 1 minute. } catch (InterruptedException e) { // ignore Thread.interrupted(); } } } /* * Similar to the start method this method will restart the default method on current thread. * This method assumes that the default application was launched at least once and that an ApplicationDescriptor * exists that can be used to relaunch the default application. */ public Object reStart(Object argument) throws Exception { ServiceReference<?> ref[] = null; ref = context.getServiceReferences("org.osgi.service.application.ApplicationDescriptor", "(eclipse.application.default=true)"); //$NON-NLS-1$//$NON-NLS-2$ if (ref != null && ref.length > 0) { Object defaultApp = context.getService(ref[0]); Method launch = defaultApp.getClass().getMethod("launch", new Class[] {Map.class}); //$NON-NLS-1$ launch.invoke(defaultApp, new Object[] {null}); return start(argument); } throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ERROR_NO_APPLICATION); } }