1 /******************************************************************************* 2 * Copyright (c) 2005, 2014 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 15 package org.eclipse.core.runtime.internal.adaptor; 16 17 import java.lang.reflect.Method; 18 import java.util.Map; 19 import java.util.concurrent.TimeUnit; 20 import org.eclipse.core.runtime.adaptor.EclipseStarter; 21 import org.eclipse.osgi.framework.log.FrameworkLog; 22 import org.eclipse.osgi.framework.log.FrameworkLogEntry; 23 import org.eclipse.osgi.internal.debug.Debug; 24 import org.eclipse.osgi.internal.framework.EquinoxConfiguration; 25 import org.eclipse.osgi.internal.framework.EquinoxContainer; 26 import org.eclipse.osgi.internal.messages.Msg; 27 import org.eclipse.osgi.service.runnable.*; 28 import org.osgi.framework.*; 29 import org.osgi.framework.launch.Framework; 30 31 public class EclipseAppLauncher implements ApplicationLauncher { 32 volatile private ParameterizedRunnable runnable = null; 33 private Object appContext = null; 34 private final java.util.concurrent.Semaphore runningLock = new java.util.concurrent.Semaphore(1); 35 private final java.util.concurrent.Semaphore waitForAppLock = new java.util.concurrent.Semaphore(0); 36 private final BundleContext context; 37 private boolean relaunch = false; 38 private final boolean failOnNoDefault; 39 private final FrameworkLog log; 40 private final EquinoxConfiguration equinoxConfig; 41 EclipseAppLauncher(BundleContext context, boolean relaunch, boolean failOnNoDefault, FrameworkLog log, EquinoxConfiguration equinoxConfig)42 public EclipseAppLauncher(BundleContext context, boolean relaunch, boolean failOnNoDefault, FrameworkLog log, EquinoxConfiguration equinoxConfig) { 43 this.context = context; 44 this.relaunch = relaunch; 45 this.failOnNoDefault = failOnNoDefault; 46 this.log = log; 47 this.equinoxConfig = equinoxConfig; 48 findRunnableService(); 49 } 50 51 /* 52 * Used for backwards compatibility with < 3.2 runtime 53 */ findRunnableService()54 private void findRunnableService() { 55 // look for a ParameterizedRunnable registered as a service by runtimes (3.0, 3.1) 56 String appClass = ParameterizedRunnable.class.getName(); 57 ServiceReference<?>[] runRefs = null; 58 try { 59 runRefs = context.getServiceReferences(ParameterizedRunnable.class.getName(), "(&(objectClass=" + appClass + ")(eclipse.application=*))"); //$NON-NLS-1$//$NON-NLS-2$ 60 } catch (InvalidSyntaxException e) { 61 // ignore this. It should never happen as we have tested the above format. 62 } 63 if (runRefs != null && runRefs.length > 0) { 64 // found the service use it as the application. 65 runnable = (ParameterizedRunnable) context.getService(runRefs[0]); 66 // we will never be able to relaunch with a pre 3.2 runtime 67 relaunch = false; 68 waitForAppLock.release(); 69 } 70 } 71 72 /* 73 * Starts this application launcher on the current thread. This method 74 * should be called by the main thread to ensure that applications are 75 * launched in the main thread. 76 */ start(Object defaultContext)77 public Object start(Object defaultContext) throws Exception { 78 // here we assume that launch has been called by runtime before we started 79 // 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 80 if (failOnNoDefault && runnable == null) 81 throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ERROR_NO_APPLICATION); 82 Object result = null; 83 boolean doRelaunch; 84 Bundle b = context.getBundle(); 85 do { 86 try { 87 if (relaunch) { 88 // need a thread to kick the main thread when the framework stops 89 final Thread mainThread = Thread.currentThread(); 90 final BundleContext mainContext = context; 91 new Thread(new Runnable() { 92 @Override 93 public void run() { 94 Framework framework = (Framework) mainContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION); 95 try { 96 framework.waitForStop(0); 97 // framework is done; tell the main thread to stop 98 mainThread.interrupt(); 99 } catch (InterruptedException e) { 100 Thread.interrupted(); 101 // just exiting now 102 } 103 } 104 }, "Framework watcher").start(); //$NON-NLS-1$ 105 106 } 107 result = runApplication(defaultContext); 108 } catch (Exception e) { 109 if (!relaunch || (b.getState() & Bundle.ACTIVE) == 0) 110 throw e; 111 if (log != null) 112 log.log(new FrameworkLogEntry(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, 0, Msg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null)); 113 } 114 doRelaunch = (relaunch && (b.getState() & Bundle.ACTIVE) != 0); 115 } while (doRelaunch); 116 return result; 117 } 118 119 /* 120 * Waits for an application to be launched and the runs the application on the 121 * current thread (main). 122 */ runApplication(Object defaultContext)123 private Object runApplication(Object defaultContext) throws Exception { 124 try { 125 // wait for an application to be launched. 126 waitForAppLock.acquire(); 127 // an application is ready; acquire the running lock. 128 // this must happen after we have acquired an application (by acquiring waitForAppLock above). 129 runningLock.acquire(); 130 if (EclipseStarter.debug) { 131 String timeString = equinoxConfig.getConfiguration("eclipse.startTime"); //$NON-NLS-1$ 132 long time = timeString == null ? 0L : Long.parseLong(timeString); 133 Debug.println("Starting application: " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ 134 } 135 try { 136 // run the actual application on the current thread (main). 137 return runnable.run(appContext != null ? appContext : defaultContext); 138 } finally { 139 // free the runnable application and release the lock to allow another app to be launched. 140 runnable = null; 141 appContext = null; 142 runningLock.release(); 143 } 144 } catch (InterruptedException e) { 145 // ignore; but mark interrupted for others 146 Thread.interrupted(); 147 } 148 return null; 149 } 150 151 @Override launch(ParameterizedRunnable app, Object applicationContext)152 public void launch(ParameterizedRunnable app, Object applicationContext) { 153 waitForAppLock.tryAcquire(); // clear out any pending apps notifications 154 if (!runningLock.tryAcquire()) // check to see if an application is currently running 155 throw new IllegalStateException("An application is aready running."); //$NON-NLS-1$ 156 this.runnable = app; 157 this.appContext = applicationContext; 158 waitForAppLock.release(); // notify the main thread to launch an application. 159 runningLock.release(); // release the running lock 160 } 161 162 @Override shutdown()163 public void shutdown() { 164 // this method will aquire and keep the runningLock to prevent 165 // all future application launches. 166 if (runningLock.tryAcquire()) 167 return; // no application is currently running. 168 ParameterizedRunnable currentRunnable = runnable; 169 if (currentRunnable instanceof ApplicationRunnable) { 170 ((ApplicationRunnable) currentRunnable).stop(); 171 try { 172 runningLock.tryAcquire(1, TimeUnit.MINUTES); // timeout after 1 minute. 173 } catch (InterruptedException e) { 174 // ignore 175 Thread.interrupted(); 176 } 177 } 178 } 179 180 /* 181 * Similar to the start method this method will restart the default method on current thread. 182 * This method assumes that the default application was launched at least once and that an ApplicationDescriptor 183 * exists that can be used to relaunch the default application. 184 */ reStart(Object argument)185 public Object reStart(Object argument) throws Exception { 186 ServiceReference<?> ref[] = null; 187 ref = context.getServiceReferences("org.osgi.service.application.ApplicationDescriptor", "(eclipse.application.default=true)"); //$NON-NLS-1$//$NON-NLS-2$ 188 if (ref != null && ref.length > 0) { 189 Object defaultApp = context.getService(ref[0]); 190 Method launch = defaultApp.getClass().getMethod("launch", new Class[] {Map.class}); //$NON-NLS-1$ 191 launch.invoke(defaultApp, new Object[] {null}); 192 return start(argument); 193 } 194 throw new IllegalStateException(Msg.ECLIPSE_STARTUP_ERROR_NO_APPLICATION); 195 } 196 } 197