Copyright (c) 2005, 2018 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 Josh Arnold - Bug 180080 Equinox Application Admin spec violations
/******************************************************************************* * Copyright (c) 2005, 2018 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 * Josh Arnold - Bug 180080 Equinox Application Admin spec violations *******************************************************************************/
package org.eclipse.equinox.internal.app; import java.util.*; import org.eclipse.core.runtime.*; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.app.IApplicationContext; import org.eclipse.osgi.service.runnable.ApplicationRunnable; import org.eclipse.osgi.service.runnable.StartupMonitor; import org.eclipse.osgi.util.NLS; import org.osgi.framework.*; import org.osgi.service.application.ApplicationException; import org.osgi.service.application.ApplicationHandle; /* * An ApplicationHandle that represents a single instance of a running eclipse application. */ public class EclipseAppHandle extends ApplicationHandle implements ApplicationRunnable, IApplicationContext { // Indicates the application is starting private static final int FLAG_STARTING = 0x01; // Indicates the application is active private static final int FLAG_ACTIVE = 0x02; // Indicates the application is stopping private static final int FLAG_STOPPING = 0x04; // Indicates the application is stopped private static final int FLAG_STOPPED = 0x08; private static final String STARTING = "org.eclipse.equinox.app.starting"; //$NON-NLS-1$ private static final String STOPPED = "org.eclipse.equinox.app.stopped"; //$NON-NLS-1$ private static final String PROP_ECLIPSE_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$ private static final Object NULL_RESULT = new Object(); private volatile ServiceRegistration handleRegistration; private int status = EclipseAppHandle.FLAG_STARTING; private final Map<String, Object> arguments; private Object application; private final Boolean defaultAppInstance; private Object result; private boolean setResult = false; private boolean setAsyncResult = false; private final boolean[] registrationLock = new boolean[] {true}; /* * Constructs a handle for a single running instance of a eclipse application. */ EclipseAppHandle(String instanceId, Map<String, Object> arguments, EclipseAppDescriptor descriptor) { super(instanceId, descriptor); defaultAppInstance = arguments == null || arguments.get(EclipseAppDescriptor.APP_DEFAULT) == null ? Boolean.FALSE : (Boolean) arguments.remove(EclipseAppDescriptor.APP_DEFAULT); if (arguments == null) this.arguments = new HashMap<>(2); else this.arguments = new HashMap<>(arguments); } @Override synchronized public String getState() { switch (status) { case FLAG_STARTING : return STARTING; case FLAG_ACTIVE : return ApplicationHandle.RUNNING; case FLAG_STOPPING : return ApplicationHandle.STOPPING; case FLAG_STOPPED : default : // must only check this if the status is STOPPED; otherwise we throw exceptions before we have set the registration. if (getServiceRegistration() == null) throw new IllegalStateException(NLS.bind(Messages.application_error_state_stopped, getInstanceId())); return STOPPED; } } @Override protected void destroySpecific() { // when this method is called we must force the application to exit. // first set the status to stopping setAppStatus(EclipseAppHandle.FLAG_STOPPING); // now force the application to stop IApplication app = getApplication(); if (app != null) app.stop(); // make sure the app status is stopped setAppStatus(EclipseAppHandle.FLAG_STOPPED); } void setServiceRegistration(ServiceRegistration sr) { synchronized (registrationLock) { this.handleRegistration = sr; registrationLock[0] = sr != null; registrationLock.notifyAll(); } } private ServiceRegistration getServiceRegistration() { synchronized (registrationLock) { if (handleRegistration == null && registrationLock[0]) { try { registrationLock.wait(1000); // timeout after 1 second } catch (InterruptedException e) { // nothing } } return handleRegistration; } } ServiceReference getServiceReference() { ServiceRegistration reg = getServiceRegistration(); if (reg == null) return null; try { return reg.getReference(); } catch (IllegalStateException e) { return null; // this will happen if the service has been unregistered already } } /* * Gets a snapshot of the current service properties. */ Dictionary<String, Object> getServiceProperties() { Dictionary<String, Object> props = new Hashtable<>(6); props.put(ApplicationHandle.APPLICATION_PID, getInstanceId()); props.put(ApplicationHandle.APPLICATION_STATE, getState()); props.put(ApplicationHandle.APPLICATION_DESCRIPTOR, getApplicationDescriptor().getApplicationId()); props.put(EclipseAppDescriptor.APP_TYPE, ((EclipseAppDescriptor) getApplicationDescriptor()).getThreadTypeString()); props.put(ApplicationHandle.APPLICATION_SUPPORTS_EXITVALUE, Boolean.TRUE); if (defaultAppInstance.booleanValue()) props.put(EclipseAppDescriptor.APP_DEFAULT, defaultAppInstance); return props; } /* * Changes the status of this handle. This method will properly transition * the state of this handle and will update the service registration accordingly. */ private synchronized void setAppStatus(int status) { if (this.status == status) return; if ((status & EclipseAppHandle.FLAG_STARTING) != 0) throw new IllegalArgumentException("Cannot set app status to starting"); //$NON-NLS-1$ // if status is stopping and the context is already stopping then return if ((status & EclipseAppHandle.FLAG_STOPPING) != 0) if ((this.status & (EclipseAppHandle.FLAG_STOPPING | EclipseAppHandle.FLAG_STOPPED)) != 0) return; // change the service properties to reflect the state change. this.status = status; ServiceRegistration handleReg = getServiceRegistration(); if (handleReg == null) return; handleReg.setProperties(getServiceProperties()); // if the status is stopped then unregister the service if ((this.status & EclipseAppHandle.FLAG_STOPPED) != 0) { ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().unlock(this); handleReg.unregister(); setServiceRegistration(null); } } @Override public Map getArguments() { return arguments; } @Override public Object run(Object context) throws Exception { if (context != null) { // always force the use of the context if it is not null arguments.put(IApplicationContext.APPLICATION_ARGS, context); } else { // get the context from the arguments context = arguments.get(IApplicationContext.APPLICATION_ARGS); if (context == null) { // if context is null then use the args from CommandLineArgs context = CommandLineArgs.getApplicationArgs(); arguments.put(IApplicationContext.APPLICATION_ARGS, context); } } Object tempResult = null; try { Object app; synchronized (this) { if ((status & (EclipseAppHandle.FLAG_STARTING | EclipseAppHandle.FLAG_STOPPING)) == 0) throw new ApplicationException(ApplicationException.APPLICATION_INTERNAL_ERROR, NLS.bind(Messages.application_instance_stopped, getInstanceId())); application = getConfiguration().createExecutableExtension("run"); //$NON-NLS-1$ app = application; notifyAll(); } if (app instanceof IApplication) tempResult = ((IApplication) app).start(this); else tempResult = EclipseAppContainer.callMethodWithException(app, "run", new Class[] {Object.class}, new Object[] {context}); //$NON-NLS-1$ if (tempResult == null) tempResult = NULL_RESULT; } finally { tempResult = setInternalResult(tempResult, false, null); } if (Activator.DEBUG) System.out.println(NLS.bind(Messages.application_returned, (new String[] {getApplicationDescriptor().getApplicationId(), tempResult == null ? "null" : tempResult.toString()}))); //$NON-NLS-1$ return tempResult; } private synchronized Object setInternalResult(Object result, boolean isAsync, IApplication tokenApp) { if (setResult) throw new IllegalStateException("The result of the application is already set."); //$NON-NLS-1$ if (isAsync) { if (!setAsyncResult) throw new IllegalStateException("The application must return IApplicationContext.EXIT_ASYNC_RESULT to set asynchronous results."); //$NON-NLS-1$ if (application != tokenApp) throw new IllegalArgumentException("The application is not the correct instance for this application context."); //$NON-NLS-1$ } else { if (result == IApplicationContext.EXIT_ASYNC_RESULT) { setAsyncResult = true; return NULL_RESULT; // the result well be set with setResult } } this.result = result; setResult = true; application = null; notifyAll(); // The application exited itself; notify the app context setAppStatus(EclipseAppHandle.FLAG_STOPPING); // must ensure the STOPPING event is fired setAppStatus(EclipseAppHandle.FLAG_STOPPED); // only set the exit code property if this is the default application // (bug 321386) only set the exit code if the result != null; when result == null we assume an exception was thrown if (isDefault() && result != null) { int exitCode = result instanceof Integer ? ((Integer) result).intValue() : 0; // Use the EnvironmentInfo Service to set properties Activator.setProperty(PROP_ECLIPSE_EXITCODE, Integer.toString(exitCode)); } return result; } @Override public void stop() { try { destroy(); } catch (IllegalStateException e) { // Do nothing; we don't care that the application was already stopped // return with no error } } @Override public void applicationRunning() { // first set the application handle status to running setAppStatus(EclipseAppHandle.FLAG_ACTIVE); // now run the splash handler final ServiceReference[] monitors = getStartupMonitors(); if (monitors == null) return; SafeRunner.run(new ISafeRunnable() { @Override public void handleException(Throwable e) { // just continue ... the exception has already been logged by // handleException(ISafeRunnable) } @Override public void run() throws Exception { for (ServiceReference m : monitors) { StartupMonitor monitor = (StartupMonitor) Activator.getContext().getService(m); if (monitor != null) { monitor.applicationRunning(); Activator.getContext().ungetService(m); } } } }); } private ServiceReference[] getStartupMonitors() { // assumes theStartupMonitor is available as a service // see EclipseStarter.publishSplashScreen ServiceReference[] refs = null; try { refs = Activator.getContext().getServiceReferences(StartupMonitor.class.getName(), null); } catch (InvalidSyntaxException e) { // ignore; this cannot happen } if (refs == null || refs.length == 0) return null; // Implement our own Comparator to sort services Arrays.sort(refs, new Comparator<ServiceReference>() { @Override public int compare(ServiceReference ref1, ServiceReference ref2) { // sort in descending order // sort based on service ranking first; highest rank wins Object property = ref1.getProperty(Constants.SERVICE_RANKING); int rank1 = (property instanceof Integer) ? ((Integer) property).intValue() : 0; property = ref2.getProperty(Constants.SERVICE_RANKING); int rank2 = (property instanceof Integer) ? ((Integer) property).intValue() : 0; if (rank1 != rank2) return rank1 > rank2 ? -1 : 1; // rankings are equal; sort by id, lowest id wins long id1 = ((Long) (ref1.getProperty(Constants.SERVICE_ID))).longValue(); long id2 = ((Long) (ref2.getProperty(Constants.SERVICE_ID))).longValue(); return id2 > id1 ? -1 : 1; } }); return refs; } private synchronized IApplication getApplication() { if (handleRegistration != null && application == null) // the handle has been initialized by the container but the launcher has not // gotten around to creating the application object and starting it yet. try { wait(5000); // timeout after a while in case there was an internal error and there will be no application created } catch (InterruptedException e) { // do nothing } return (IApplication) ((application instanceof IApplication) ? application : null); } private IConfigurationElement getConfiguration() { IExtension applicationExtension = ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().getAppExtension(getApplicationDescriptor().getApplicationId()); if (applicationExtension == null) throw new RuntimeException(NLS.bind(Messages.application_notFound, getApplicationDescriptor().getApplicationId(), ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().getAvailableAppsMsg())); IConfigurationElement[] configs = applicationExtension.getConfigurationElements(); if (configs.length == 0) throw new RuntimeException(NLS.bind(Messages.application_invalidExtension, getApplicationDescriptor().getApplicationId())); return configs[0]; } @Override public String getBrandingApplication() { IBranding branding = ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().getBranding(); return branding == null ? null : branding.getApplication(); } @Override public Bundle getBrandingBundle() { IBranding branding = ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().getBranding(); return branding == null ? null : branding.getDefiningBundle(); } @Override public String getBrandingDescription() { IBranding branding = ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().getBranding(); return branding == null ? null : branding.getDescription(); } @Override public String getBrandingId() { IBranding branding = ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().getBranding(); return branding == null ? null : branding.getId(); } @Override public String getBrandingName() { IBranding branding = ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().getBranding(); return branding == null ? null : branding.getName(); } @Override public String getBrandingProperty(String key) { IBranding branding = ((EclipseAppDescriptor) getApplicationDescriptor()).getContainerManager().getBranding(); return branding == null ? null : branding.getProperty(key); } boolean isDefault() { return defaultAppInstance.booleanValue(); } public synchronized Object waitForResult(int timeout) { try { return getExitValue(timeout); } catch (ApplicationException | InterruptedException e) { // return null } return null; } @Override public synchronized Object getExitValue(long timeout) throws ApplicationException, InterruptedException { if (handleRegistration == null && application == null) return result; long startTime = System.currentTimeMillis(); long delay = timeout; while (!setResult && (delay > 0 || timeout == 0)) { wait(delay); // only wait for the specified amount of time if (timeout > 0) delay -= (System.currentTimeMillis() - startTime); } if (result == null) throw new ApplicationException(ApplicationException.APPLICATION_EXITVALUE_NOT_AVAILABLE); if (result == NULL_RESULT) return null; return result; } @Override public void setResult(Object result, IApplication application) { setInternalResult(result == null ? NULL_RESULT : result, true, application); } }