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