| /******************************************************************************* |
| * Copyright (c) 2005, 2014 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 |
| *******************************************************************************/ |
| |
| 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; |
| } |
| |
| @Override |
| 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 |
| } |
| |
| @Override |
| 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); |
| } |
| } |