| /******************************************************************************* |
| * Copyright (c) 2005, 2006 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.equinox.internal.app; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.*; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.equinox.app.IContainer; |
| import org.eclipse.equinox.registry.*; |
| import org.eclipse.osgi.framework.log.FrameworkLog; |
| import org.eclipse.osgi.framework.log.FrameworkLogEntry; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.*; |
| import org.osgi.service.application.ApplicationDescriptor; |
| import org.osgi.service.application.ApplicationHandle; |
| import org.osgi.util.tracker.ServiceTracker; |
| |
| /* |
| * A MEG application container that understands eclipse applications. This |
| * container will discover installed eclipse applications and register the |
| * appropriate ApplicatoinDescriptor service with the service registry. |
| * It also manages container extensions which are uses to launch |
| * different application types. |
| */ |
| public class ContainerManager implements IRegistryChangeListener, SynchronousBundleListener { |
| private static final String PI_RUNTIME = "org.eclipse.core.runtime"; //$NON-NLS-1$ |
| private static final String PT_APPLICATIONS = "applications"; //$NON-NLS-1$ |
| private static final String PT_APP_TYPE = "type"; //$NON-NLS-1$ |
| private static final String PT_APP_VISIBLE = "visible"; //$NON-NLS-1$ |
| private static final String PT_APP_SINGLETON = "singleton"; //$NON-NLS-1$ |
| private static final String PT_RUN = "run"; //$NON-NLS-1$ |
| private static final String PT_PRODUCTS = "products"; //$NON-NLS-1$ |
| private static final String PT_CONTAINERS = "containers"; //$NON-NLS-1$ |
| private static final String ATTR_APPLICATION = "application"; //$NON-NLS-1$ |
| private static final String EXT_ERROR_APP = "org.eclipse.equinox.app.error"; //$NON-NLS-1$ |
| static final String APP_TYPE_MAIN_THREAD = "main.thread"; //$NON-NLS-1$ |
| |
| public static final String PROP_PRODUCT = "eclipse.product"; //$NON-NLS-1$ |
| public static final String PROP_ECLIPSE_APPLICATION = "eclipse.application"; //$NON-NLS-1$ |
| public static final String PROP_ECLIPSE_APPLICATION_ARGS = "eclipse.application.args"; //$NON-NLS-1$ |
| public static final String PROP_ECLIPSE_APPLICATION_NODEFAULT = "eclipse.application.noDefault"; //$NON-NLS-1$ |
| |
| BundleContext context; |
| // A map of ApplicationDescriptors keyed by eclipse application ID |
| private HashMap apps = new HashMap(); |
| // A map of containers keyed by application type |
| private HashMap containers = new HashMap(); |
| // tracks the FrameworkLog |
| private ServiceTracker frameworkLog; |
| private boolean missingProductReported; |
| private IExtensionRegistry extensionRegistry; |
| |
| public ContainerManager(BundleContext context, IExtensionRegistry extensionRegistry) { |
| this.context = context; |
| this.extensionRegistry = extensionRegistry; |
| } |
| |
| void startManager() { |
| frameworkLog = new ServiceTracker(context, FrameworkLog.class.getName(), null); |
| frameworkLog.open(); |
| getExtensionRegistry().addRegistryChangeListener(this); |
| registerAppDecriptors(); |
| // need to listen for system bundle stopping |
| context.addBundleListener(this); |
| // Start the default application |
| startDefaultApp(); |
| } |
| |
| void stopManager() { |
| // stop all applications and containers first |
| stopAll(); |
| context.removeBundleListener(this); |
| getExtensionRegistry().removeRegistryChangeListener(this); |
| frameworkLog.close(); |
| frameworkLog = null; |
| // flush the apps and containers |
| apps.clear(); |
| containers.clear(); |
| } |
| |
| /* |
| * Only used to find the default application |
| */ |
| private EclipseAppDescriptor getAppDescriptor(String applicationId) { |
| EclipseAppDescriptor result = null; |
| synchronized (apps) { |
| result = (EclipseAppDescriptor) apps.get(applicationId); |
| } |
| if (result == null) { |
| registerAppDecriptors(); // try again just in case we are waiting for an event |
| synchronized (apps) { |
| result = (EclipseAppDescriptor) apps.get(applicationId); |
| } |
| } |
| return result; |
| } |
| |
| EclipseAppDescriptor[] getAppDescriptorsByType(String type) { |
| ArrayList result = new ArrayList(); |
| synchronized (apps) { |
| for (Iterator iApps = apps.values().iterator(); iApps.hasNext();) { |
| EclipseAppDescriptor app = (EclipseAppDescriptor) iApps.next(); |
| if (type.equals(app.getType())) |
| result.add(app); |
| } |
| } |
| return (EclipseAppDescriptor[]) result.toArray(new EclipseAppDescriptor[result.size()]); |
| } |
| |
| private IContainer getContainer(String type) { |
| synchronized (containers) { |
| IContainer container = (IContainer) containers.get(type); |
| if (container != null) |
| return container; |
| container = createContainer(type); |
| if (container != null) |
| containers.put(type, container); |
| return container; |
| } |
| } |
| |
| private EclipseAppDescriptor createAppDescriptor(IExtension appExtension) { |
| synchronized (apps) { |
| EclipseAppDescriptor appDescriptor = (EclipseAppDescriptor) apps.get(appExtension.getUniqueIdentifier()); |
| if (appDescriptor != null) |
| return appDescriptor; |
| // the appDescriptor does not exist for the app ID; create it |
| IConfigurationElement[] configs = appExtension.getConfigurationElements(); |
| String type = null; |
| boolean visible = true; |
| if (configs.length > 0) { |
| type = configs[0].getAttribute(PT_APP_TYPE); |
| String sVisible = configs[0].getAttribute(PT_APP_VISIBLE); |
| visible = sVisible == null ? true : Boolean.valueOf(sVisible).booleanValue(); |
| } |
| appDescriptor = new EclipseAppDescriptor(appExtension.getNamespace(), appExtension.getUniqueIdentifier(), type, visible, this); |
| // register the appDescriptor as a service |
| ServiceRegistration sr = (ServiceRegistration) AccessController.doPrivileged(new RegisterService(ApplicationDescriptor.class.getName(), appDescriptor, appDescriptor.getServiceProperties())); |
| appDescriptor.setServiceRegistration(sr); |
| // save the app descriptor in the cache |
| apps.put(appExtension.getUniqueIdentifier(), appDescriptor); |
| return appDescriptor; |
| } |
| } |
| |
| private EclipseAppDescriptor removeAppDescriptor(String applicationId) { |
| synchronized (apps) { |
| EclipseAppDescriptor appDescriptor = (EclipseAppDescriptor) apps.remove(applicationId); |
| if (appDescriptor == null) |
| return null; |
| appDescriptor.unregister(); |
| return appDescriptor; |
| } |
| } |
| |
| /* |
| * Gives access to the RegisterService privileged action. |
| */ |
| PrivilegedAction getRegServiceAction(String serviceClass, Object serviceObject, Dictionary serviceProps) { |
| return new RegisterService(serviceClass, serviceObject, serviceProps); |
| } |
| |
| /* |
| * PrivilegedAction used to register ApplicationDescriptor and ApplicationHandle services |
| */ |
| private class RegisterService implements PrivilegedAction { |
| String serviceClass; |
| Object serviceObject; |
| Dictionary serviceProps; |
| |
| RegisterService(String serviceClass, Object serviceObject, Dictionary serviceProps) { |
| this.serviceClass = serviceClass; |
| this.serviceObject = serviceObject; |
| this.serviceProps = serviceProps; |
| } |
| |
| public Object run() { |
| return context.registerService(serviceClass, serviceObject, serviceProps); |
| } |
| } |
| |
| private void startDefaultApp() { |
| if (!Boolean.getBoolean(ContainerManager.PROP_ECLIPSE_APPLICATION_NODEFAULT)) { |
| // find the default application |
| EclipseAppDescriptor defaultDesc = findDefaultApp(); |
| // if the default app is a main thread one then we have to just tell the |
| // main thread container about the default app so it can launch it when it is ready |
| if (APP_TYPE_MAIN_THREAD.equals(defaultDesc.getType())) { |
| MainThreadContainer container = (MainThreadContainer) ((SingletonContainer) getContainer(APP_TYPE_MAIN_THREAD)).getContainer(); |
| container.setDefaultApp(defaultDesc); |
| } else |
| // just launch the default application |
| try { |
| defaultDesc.launch(null); |
| } catch (Exception e) { |
| // TODO should log this!! |
| } |
| } |
| } |
| |
| private EclipseAppDescriptor findDefaultApp() { |
| String applicationId = System.getProperty(PROP_ECLIPSE_APPLICATION); |
| if (applicationId == null) { |
| //Derive the application from the product information |
| applicationId = getProductAppId(); |
| if (applicationId != null) |
| // use the long way to set the property to compile against eeminimum |
| System.getProperties().setProperty(PROP_ECLIPSE_APPLICATION, applicationId); |
| } |
| if (applicationId == null) { |
| // the application id is not set; return a descriptor that will throw an exception |
| // return new EclipseAppDescriptor(Activator.PI_APP, Activator.PI_APP + ".missingapp", null, false, this, new RuntimeException(Messages.application_noIdFound)); //$NON-NLS-1$ |
| ErrorApplication.setError(new RuntimeException(Messages.application_noIdFound)); |
| return getAppDescriptor(EXT_ERROR_APP); |
| } |
| EclipseAppDescriptor defaultApp = getAppDescriptor(applicationId); |
| if (defaultApp == null) { |
| // the application id is not available in the registry; return a descriptor that will throw an exception |
| //return new EclipseAppDescriptor(applicationId, applicationId, null, false, this, new RuntimeException(NLS.bind(Messages.application_notFound, applicationId, getAvailableAppsMsg(getExtensionRegistry())))); |
| ErrorApplication.setError(new RuntimeException(NLS.bind(Messages.application_notFound, applicationId, getAvailableAppsMsg(getExtensionRegistry())))); |
| return getAppDescriptor(EXT_ERROR_APP); |
| } |
| return defaultApp; |
| } |
| |
| /* |
| * Registers an ApplicationDescriptor service for each eclipse application |
| * available in the extension registry. |
| */ |
| private void registerAppDecriptors() { |
| // look in the old core.runtime applications extension point |
| IExtension[] availableApps = getAvailableApps(getExtensionRegistry(), PI_RUNTIME); |
| for (int i = 0; i < availableApps.length; i++) |
| createAppDescriptor(availableApps[i]); |
| // look in the new equinox.app applications extinsion point |
| availableApps = getAvailableApps(getExtensionRegistry(), Activator.PI_APP); |
| for (int i = 0; i < availableApps.length; i++) |
| createAppDescriptor(availableApps[i]); |
| } |
| |
| private IContainer createContainer(String type) { |
| IExtensionPoint extPoint = getExtensionRegistry().getExtensionPoint(Activator.PI_APP, PT_CONTAINERS); |
| if (extPoint == null) |
| return null; |
| IExtension[] availableContainers = extPoint.getExtensions(); |
| for (int i = 0; i < availableContainers.length; i++) { |
| IConfigurationElement[] configs = availableContainers[i].getConfigurationElements(); |
| if (configs.length == 0) |
| return null; |
| String containerType = configs[0].getAttribute(PT_APP_TYPE); |
| if (type.equals(containerType)) { |
| boolean singleton = Boolean.valueOf(configs[0].getAttribute(PT_APP_SINGLETON)).booleanValue(); |
| return createContainer(configs[0], type, singleton); |
| } |
| } |
| return null; |
| } |
| |
| private IContainer createContainer(IConfigurationElement config, String type, boolean singleton) { |
| try { |
| IContainer container = (IContainer) config.createExecutableExtension(PT_RUN); |
| if (singleton) |
| container = new SingletonContainer(container, type, this); |
| return container; |
| } catch (CoreException e) { |
| // TODO should log this |
| e.printStackTrace(); |
| } |
| return null; |
| } |
| |
| private IContainer removeContainer(IExtension containerExt) { |
| IConfigurationElement[] configs = containerExt.getConfigurationElements(); |
| if (configs.length == 0) |
| return null; |
| String type = configs[0].getAttribute(PT_APP_TYPE); |
| if (type == null) |
| return null; |
| // stop all applications for the container first |
| stopAllApplications(type); |
| IContainer result = null; |
| synchronized (containers) { |
| result = (IContainer) containers.remove(type); |
| } |
| // shutdown the container |
| if (result != null) |
| result.shutdown(); |
| return result; |
| } |
| |
| /* |
| * Returns a list of all the available application IDs which are available |
| * in the extension registry. |
| */ |
| private IExtension[] getAvailableApps(IExtensionRegistry registry, String pi) { |
| IExtensionPoint point = registry.getExtensionPoint(pi + '.' + PT_APPLICATIONS); |
| if (point == null) |
| return new IExtension[0]; |
| return point.getExtensions(); |
| } |
| |
| private String getAvailableAppsMsg(IExtensionRegistry registry) { |
| IExtension[] availableApps = getAvailableApps(registry, PI_RUNTIME); |
| String availableAppsMsg = "<NONE>"; //$NON-NLS-1$ |
| if (availableApps.length != 0) { |
| availableAppsMsg = availableApps[0].getUniqueIdentifier(); |
| for (int i = 1; i < availableApps.length; i++) |
| availableAppsMsg = availableAppsMsg + ", " + availableApps[i].getUniqueIdentifier(); //$NON-NLS-1$ |
| } |
| availableApps = getAvailableApps(registry, Activator.PI_APP); |
| if (availableApps.length != 0) { |
| if (!availableAppsMsg.equals("<NONE>")) //$NON-NLS-1$ |
| availableAppsMsg = availableAppsMsg + ", "; //$NON-NLS-1$ |
| availableAppsMsg = availableAppsMsg + availableApps[0].getUniqueIdentifier(); |
| for (int i = 1; i < availableApps.length; i++) |
| availableAppsMsg = availableAppsMsg + ", " + availableApps[i].getUniqueIdentifier(); //$NON-NLS-1$ |
| } |
| return availableAppsMsg; |
| } |
| |
| /* |
| * Returns the application extension for the specified applicaiton ID. |
| * A RuntimeException is thrown if the extension does not exist for the |
| * given application ID. |
| */ |
| IExtension getAppExtension(String applicationId) { |
| IExtensionRegistry registry = getExtensionRegistry(); |
| IExtension applicationExtension = registry.getExtension(PI_RUNTIME, PT_APPLICATIONS, applicationId); |
| if (applicationExtension == null) |
| applicationExtension = registry.getExtension(Activator.PI_APP, PT_APPLICATIONS, applicationId); |
| if (applicationExtension == null) |
| throw new RuntimeException(NLS.bind(Messages.application_notFound, applicationId, getAvailableAppsMsg(registry))); |
| return applicationExtension; |
| } |
| |
| private IExtensionRegistry getExtensionRegistry() { |
| return extensionRegistry; |
| } |
| |
| private FrameworkLog getFrameworkLog() { |
| return (FrameworkLog) AppPersistenceUtil.getService(frameworkLog); |
| } |
| |
| private String getProductAppId() { |
| String productId = System.getProperty(PROP_PRODUCT); |
| if (productId == null) |
| return null; |
| IConfigurationElement[] entries = getExtensionRegistry().getConfigurationElementsFor(PI_RUNTIME, PT_PRODUCTS, productId); |
| if (entries.length > 0) |
| // There should only be one product with the given id so just take the first element |
| return entries[0].getAttribute(ATTR_APPLICATION); |
| IConfigurationElement[] elements = getExtensionRegistry().getConfigurationElementsFor(PI_RUNTIME, PT_PRODUCTS); |
| List logEntries = null; |
| for (int i = 0; i < elements.length; i++) { |
| IConfigurationElement element = elements[i]; |
| if (element.getName().equalsIgnoreCase("provider")) { //$NON-NLS-1$ |
| try { |
| Object provider = element.createExecutableExtension(PT_RUN); |
| Object[] products = (Object[]) ContainerManager.execMethod(provider, "getProducts", null, null); //$NON-NLS-1$ |
| for (int j = 0; j < products.length; j++) { |
| Object provided = products[j]; |
| if (productId.equalsIgnoreCase((String) ContainerManager.execMethod(provided, "getId", null, null))) //$NON-NLS-1$ |
| return (String) ContainerManager.execMethod(provided, "getApplication", null, null); //$NON-NLS-1$ |
| } |
| } catch (CoreException e) { |
| if (logEntries == null) |
| logEntries = new ArrayList(3); |
| logEntries.add(new FrameworkLogEntry(Activator.PI_APP, NLS.bind(Messages.provider_invalid, element.getParent().toString()), 0, e, null)); |
| } |
| } |
| } |
| if (logEntries != null) |
| getFrameworkLog().log(new FrameworkLogEntry(Activator.PI_APP, Messages.provider_invalid_general, 0, null, (FrameworkLogEntry[]) logEntries.toArray())); |
| |
| if (!missingProductReported) { |
| getFrameworkLog().log(new FrameworkLogEntry(Activator.PI_APP, NLS.bind(Messages.product_notFound, productId), 0, null, null)); |
| missingProductReported = true; |
| } |
| return null; |
| } |
| |
| public static Object execMethod(Object obj, String methodName, Class argType, Object arg) { |
| try { |
| Method method = obj.getClass().getMethod(methodName, argType == null ? null : new Class[] {argType}); |
| return method.invoke(obj, arg == null ? null : new Object[] {arg}); |
| } catch (NoSuchMethodException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } catch (IllegalArgumentException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } catch (IllegalAccessException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } catch (InvocationTargetException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| } |
| |
| void launch(EclipseAppHandle appHandle) throws Exception { |
| String type = ((EclipseAppDescriptor) appHandle.getApplicationDescriptor()).getType(); |
| if (type == null) |
| type = APP_TYPE_MAIN_THREAD; |
| IContainer container = getContainer(type); |
| if (container != null) |
| appHandle.setApplication(container.launch(appHandle)); |
| else |
| throw new UnsupportedOperationException(NLS.bind(Messages.container_notFound, appHandle.getApplicationDescriptor().getApplicationId(), type)); |
| } |
| |
| public void registryChanged(IRegistryChangeEvent event) { |
| processAppDeltas(event.getExtensionDeltas(PI_RUNTIME, PT_APPLICATIONS)); |
| processAppDeltas(event.getExtensionDeltas(Activator.PI_APP, PT_APPLICATIONS)); |
| processContainerDeltas(event.getExtensionDeltas(Activator.PI_APP, PT_CONTAINERS)); |
| } |
| |
| private void processAppDeltas(IExtensionDelta[] deltas) { |
| for (int i = 0; i < deltas.length; i++) { |
| switch (deltas[i].getKind()) { |
| case IExtensionDelta.ADDED : |
| createAppDescriptor(deltas[i].getExtension()); |
| break; |
| case IExtensionDelta.REMOVED : |
| removeAppDescriptor(deltas[i].getExtension().getUniqueIdentifier()); |
| break; |
| } |
| } |
| } |
| |
| private void processContainerDeltas(IExtensionDelta[] deltas) { |
| for (int i = 0; i < deltas.length; i++) { |
| switch (deltas[i].getKind()) { |
| case IExtensionDelta.ADDED : |
| // don't create containers agressively |
| break; |
| case IExtensionDelta.REMOVED : |
| removeContainer(deltas[i].getExtension()); |
| break; |
| } |
| } |
| } |
| |
| public void bundleChanged(BundleEvent event) { |
| // if this is not the system bundle stopping then ignore the event |
| if ((BundleEvent.STOPPING & event.getType()) == 0 || event.getBundle().getBundleId() != 0) |
| return; |
| // The system bundle is stopping; better stop all applications and containers now |
| stopAll(); |
| } |
| |
| private void stopAllApplications(String type) { |
| try { |
| ServiceReference[] runningRefs = context.getServiceReferences(ApplicationHandle.class.getName(), "(&(eclipse.application.type=" + type + ")(!(application.state=STOPPING)))"); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (runningRefs != null) |
| for (int i = 0; i < runningRefs.length; i++) { |
| ApplicationHandle handle = (ApplicationHandle) context.getService(runningRefs[i]); |
| try { |
| handle.destroy(); |
| } catch (Throwable t) { |
| // just ignore; nothing we can really do and we need to continue |
| } |
| } |
| } catch (InvalidSyntaxException e) { |
| // do nothing; we already tested the filter string above |
| } |
| } |
| |
| private void stopAll() { |
| // get a stapshot of running applications |
| try { |
| ServiceReference[] runningRefs = context.getServiceReferences(ApplicationHandle.class.getName(), "(!(application.state=STOPPING))"); //$NON-NLS-1$ |
| if (runningRefs != null) |
| for (int i = 0; i < runningRefs.length; i++) { |
| ApplicationHandle handle = (ApplicationHandle) context.getService(runningRefs[i]); |
| try { |
| handle.destroy(); |
| } catch (Throwable t) { |
| // TODO should log this |
| } |
| } |
| } catch (InvalidSyntaxException e) { |
| // do nothing; we already tested the filter string above |
| } |
| // shutdown all containers |
| synchronized (containers) { |
| for (Iterator iContainers = containers.values().iterator(); iContainers.hasNext();) |
| ((IContainer) iContainers.next()).shutdown(); |
| } |
| } |
| } |