blob: bde422624350166869bc163ab7db9b42fe52cf70 [file] [log] [blame]
/**
* Copyright (c) 2008 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.egf.core.session;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IRegistryEventListener;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.egf.common.helper.BundleHelper;
import org.eclipse.egf.common.helper.JavaHelper;
import org.eclipse.egf.core.EGFCoreDebug;
import org.eclipse.egf.core.EGFCorePlugin;
import org.eclipse.egf.core.l10n.EGFCoreMessages;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.osgi.baseadaptor.BaseData;
import org.eclipse.osgi.framework.internal.core.AbstractBundle;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.BundleSpecification;
import org.eclipse.osgi.service.resolver.ExportPackageDescription;
import org.eclipse.osgi.service.resolver.ImportPackageSpecification;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.core.plugin.PluginRegistry;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.PackageAdmin;
/**
* @author Xavier Maysonnave
*
*/
public final class ProjectBundleSession {
private static final String EGF_TARGET_BUNDLE_PRIORITY = "egf.target.bundle.priority"; //$NON-NLS-1$
public static String PROJECT_BUNDLE_SESSION = "org.eclipse.egf.core.project.bundle.session"; //$NON-NLS-1$
private BundleContext _context;
private Map<String, Bundle> _projectBundles = new HashMap<String, Bundle>();
private List<String> _uninstalled = new UniqueEList<String>();
private Boolean runtimeBundlePriority;
public static String getLocation(IPluginModelBase model) throws CoreException {
if (model == null) {
return null;
}
IResource resource = model.getUnderlyingResource();
if (resource == null) {
return null;
}
String location = null;
try {
location = "reference:" //$NON-NLS-1$
+ URLDecoder.decode(resource.getProject().getLocationURI().toURL().toExternalForm(), System.getProperty("file.encoding")); //$NON-NLS-1$
} catch (Throwable t) {
throw new CoreException(EGFCorePlugin.getDefault().newStatus(IStatus.ERROR, NLS.bind(EGFCoreMessages.ProjectBundleSession_URLFailure, resource.getProject().getName()), t));
}
return location;
}
public ProjectBundleSession(BundleContext context) {
Assert.isNotNull(context);
_context = context;
}
private void installWorkspaceModels(List<Bundle> bundles, List<IPluginModelBase> workspaceModels) throws CoreException {
if (bundles == null || workspaceModels == null || workspaceModels.size() == 0) {
return;
}
// Install Workspace models
for (IPluginModelBase workspaceModel : workspaceModels) {
// Ignore already installed bundle
if (_projectBundles.get(getLocation(workspaceModel)) != null) {
continue;
}
// Retrieve base location
String location = getLocation(workspaceModel);
// Install the bundle
Bundle bundle = installBundle(location);
// Add output folders if any
IProject project = workspaceModel.getUnderlyingResource().getProject();
addOutputFoldersToBundleClasspath(project, bundle);
// Store
bundles.add(bundle);
if (EGFCoreDebug.isDebugBundleSession()) {
if (_uninstalled.size() == 0 && _projectBundles.size() == 0) {
EGFCorePlugin.getDefault().logInfo("Start ProjectBundleSession..."); //$NON-NLS-1$
}
}
_projectBundles.put(location, bundle);
}
}
/**
* Installs the bundle corresponding to the model.
*
* @param model
* Model of the bundle to be installed.
*/
private Bundle installBundle(IPluginModelBase model) throws CoreException {
// We only load workspace model
if (getLocation(model) == null) {
return null;
}
final String bundleId = BundleHelper.getBundleId(model);
if (bundleId == null) {
return null;
}
// Gather workspace bundles to install
final List<IPluginModelBase> workspaceModels = getWorkspaceModelDependencies(model);
// Check whether or not such workspace models have extension points
final List<String> registryContributors = new UniqueEList<String>();
for (IPluginModelBase workspaceModel : workspaceModels) {
if (workspaceModel.getPluginBase().getExtensions() != null && workspaceModel.getPluginBase().getExtensions().length > 0) {
String workspaceBundleId = BundleHelper.getBundleId(workspaceModel);
if (workspaceBundleId != null) {
registryContributors.add(workspaceBundleId);
}
}
}
// State for thread synchronization
final boolean[] session = new boolean[] {
false
};
// build a listener to wait for asynchronous extensions notifications
IRegistryEventListener registryListener = null;
if (registryContributors.isEmpty() == false) {
registryListener = new IRegistryEventListener() {
public void removed(IExtension[] extensions) {
// Nothing to do
}
public void added(IExtension[] extensions) {
// Process
for (IExtension extension : extensions) {
registryContributors.remove(extension.getContributor().getName());
if (registryContributors.isEmpty()) {
synchronized (session) {
session[0] = true;
session.notifyAll();
}
}
}
}
public void added(IExtensionPoint[] extensionPoints) {
// Nothing to do
}
public void removed(IExtensionPoint[] extensionPoints) {
// Nothing to do
}
};
// Register
RegistryFactory.getRegistry().addListener(registryListener);
}
// Install workspace bundles
final List<Bundle> bundles = new UniqueEList<Bundle>();
internalInstallBundle(bundles, workspaceModels);
// Wait for extensions notifications if applicable
if (registryListener != null) {
synchronized (session) {
while (session[0] == false) {
try {
session.wait();
} catch (InterruptedException e) {
break;
}
}
}
// Unregister
RegistryFactory.getRegistry().removeListener(registryListener);
}
// Trace
if (EGFCoreDebug.isDebugBundleSession()) {
for (Bundle bundle : bundles) {
EGFCorePlugin.getDefault().logInfo(NLS.bind("Workspace Bundle ''{0}'' is started.", bundle.getSymbolicName())); //$NON-NLS-1$
}
}
// Return our new bundle
return Platform.getBundle(bundleId);
}
private void internalInstallBundle(List<Bundle> bundles, List<IPluginModelBase> workspaceModels) throws CoreException {
// Uninstall runtime bundles if any
uninstallRuntimeBundle(workspaceModels);
// Install workspace bundles
final List<Bundle> workspaceBundles = new ArrayList<Bundle>(bundles);
installWorkspaceModels(bundles, workspaceModels);
// Refresh installed workspace bundles if any
if (bundles.isEmpty() == false) {
refreshPackages(bundles.toArray(new Bundle[bundles.size()]));
}
// Start them
for (Bundle bundle : bundles) {
try {
if (bundle.getState() != Bundle.ACTIVE) {
bundle.start(Bundle.START_TRANSIENT);
} else {
workspaceBundles.remove(bundle);
}
} catch (Throwable t) {
throw new CoreException(EGFCorePlugin.getDefault().newStatus(IStatus.ERROR, NLS.bind(EGFCoreMessages.ProjectBundleSession_StartFailure, bundle), t));
}
}
}
private void uninstallRuntimeBundle(List<IPluginModelBase> workspaceModels) throws CoreException {
if (workspaceModels == null || workspaceModels.isEmpty()) {
return;
}
List<IPluginModelBase> models = new UniqueEList<IPluginModelBase>();
// Uninstall Runtime Bundle
List<Bundle> bundles = new UniqueEList<Bundle>();
for (IPluginModelBase workspaceModel : workspaceModels) {
// Ignore already uninstalled bundle
if (_projectBundles.get(getLocation(workspaceModel)) != null) {
continue;
}
// Uninstall Runtime if any
String bundleId = BundleHelper.getBundleId(workspaceModel);
if (bundleId == null) {
continue;
}
Bundle bundle = Platform.getBundle(bundleId);
if (bundle == null) {
continue;
}
if (bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED || bundle.getState() == Bundle.STARTING || bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE) {
uninstallBundle(bundle);
}
// Store
models.add(workspaceModel);
_uninstalled.add(bundle.getLocation());
bundles.add(bundle);
// Trace
if (EGFCoreDebug.isDebugBundleSession()) {
if (_uninstalled.size() == 0) {
EGFCorePlugin.getDefault().logInfo("Start ProjectBundleSession..."); //$NON-NLS-1$
}
}
}
// Refresh uninstalled runtime bundles if any
if (models.isEmpty() == false) {
refreshPackages(bundles.toArray(new Bundle[bundles.size()]));
if (EGFCoreDebug.isDebugBundleSession()) {
for (IPluginModelBase model : models) {
EGFCorePlugin.getDefault().logInfo(NLS.bind("Runtime Bundle ''{0}'' is uninstalled.", BundleHelper.getBundleId(model))); //$NON-NLS-1$
}
}
}
return;
}
private List<IPluginModelBase> getWorkspaceModelDependencies(IPluginModelBase base) throws CoreException {
List<IPluginModelBase> dependencies = new UniqueEList<IPluginModelBase>();
// Store base
dependencies.add(base);
// Analyse base
BundleDescription description = base.getBundleDescription();
if (description == null) {
return dependencies;
}
for (BundleSpecification requiredBundle : description.getRequiredBundles()) {
for (IPluginModelBase workspaceModel : PluginRegistry.getWorkspaceModels()) {
if (requiredBundle.isSatisfiedBy(workspaceModel.getBundleDescription())) {
dependencies.addAll(getWorkspaceModelDependencies(workspaceModel));
break;
}
}
}
for (ImportPackageSpecification importPackage : description.getImportPackages()) {
for (IPluginModelBase workspaceModel : PluginRegistry.getWorkspaceModels()) {
for (ExportPackageDescription export : workspaceModel.getBundleDescription().getExportPackages()) {
if (importPackage.isSatisfiedBy(export)) {
dependencies.addAll(getWorkspaceModelDependencies(workspaceModel));
break;
}
}
}
}
if (isRuntimeBundlePriority()) {
for (IPluginModelBase innerBase : new ArrayList<IPluginModelBase>(dependencies)) {
if (Platform.getBundle(innerBase.getBundleDescription().getSymbolicName()) != null) {
dependencies.remove(innerBase);
}
}
}
return dependencies;
}
/**
* Installs the bundle corresponding to the given location. This will fail
* if
* the location doesn't point
* to a valid bundle.
*
* @param location
* Location of the bundle to be installed.
* @return The installed bundle.
* @throws BundleException
* Thrown if the Bundle isn't valid.
* @throws IllegalStateException
* Thrown if the bundle couldn't be installed properly.
*/
private Bundle installBundle(String location) throws CoreException {
Bundle bundle = null;
try {
bundle = _context.installBundle(location);
} catch (Throwable t) {
throw new CoreException(EGFCorePlugin.getDefault().newStatus(IStatus.ERROR, NLS.bind(EGFCoreMessages.ProjectBundleSession_InstallationFailure, location), t));
}
// Not sure if it's needed, anyway we are conservative on that one
if (bundle == null) {
throw new CoreException(EGFCorePlugin.getDefault().newStatus(IStatus.ERROR, NLS.bind(EGFCoreMessages.ProjectBundleSession_InstallationFailure, location), null));
}
int state = bundle.getState();
if (state != Bundle.INSTALLED) {
throw new CoreException(EGFCorePlugin.getDefault().newStatus(IStatus.ERROR, NLS.bind(EGFCoreMessages.ProjectBundleSession_IllegalBundleState, bundle, state), null));
}
return bundle;
}
/**
* This will set the equinox classpath of <code>bundle</code> to reflect the
* eclipse classpath of <code>plugin</code>.
*
* @param project
* The eclipse plugin which classpath is to be set for its
* corresponding equinox bundle.
* @param bundle
* The equinox bundle which classpath is to reflect an eclipse
* development plugin.
*/
private void addOutputFoldersToBundleClasspath(IProject project, Bundle bundle) throws CoreException {
List<String> outputFolders = JavaHelper.getStringOutputFolders(JavaCore.create(project));
BaseData bundleData = (BaseData) ((AbstractBundle) bundle).getBundleData();
StringBuilder classpath = new StringBuilder();
if (outputFolders.size() == 0) {
classpath.append("."); //$NON-NLS-1$
} else {
for (Iterator<String> it = outputFolders.iterator(); it.hasNext();) {
if (classpath.length() > 0) {
classpath.append(","); //$NON-NLS-1$
}
classpath.append(it.next());
}
}
bundleData.setClassPathString(classpath.toString());
}
/**
* Returns the bundle corresponding to the IProject if any.
*
* @param id
* The plug-in ID of the bundle we seek.
* @return The bundle corresponding to the given location if any,
* <code>null</code> otherwise.
*/
public Bundle getBundle(String id) throws CoreException {
if (id == null || id.trim().length() == 0) {
return null;
}
IPluginModelBase model = PluginRegistry.findModel(id.trim());
if (model == null) {
return null;
}
// Check if we face a non workspace model
if (model.getUnderlyingResource() == null || isRuntimeBundlePriority()) {
return Platform.getBundle(id.trim());
}
String location = getLocation(model);
if (location == null) {
return null;
}
// Workspace model
Bundle bundle = _projectBundles.get(location);
if (bundle == null) {
return installBundle(model);
}
return bundle;
}
/**
* Returns the bundle corresponding to the IProject if any.
*
* @param project
* The IProject of the bundle we seek.
* @return The bundle corresponding to the given location if any,
* <code>null</code> otherwise.
*/
public Bundle getBundle(IProject project) throws CoreException {
if (project == null) {
return null;
}
IPluginModelBase model = PluginRegistry.findModel(project);
if (model == null) {
return null;
}
if (isRuntimeBundlePriority()) {
String id = BundleHelper.getBundleId(model);
if (id != null) {
Bundle bundle = Platform.getBundle(id);
if (bundle != null) {
return bundle;
}
}
}
String location = getLocation(model);
if (location == null) {
return null;
}
Bundle bundle = _projectBundles.get(location);
if (bundle == null) {
bundle = installBundle(model);
}
return bundle;
}
/**
* Refreshes all exported packages of the given bundles. This must be called
* after installing the bundle.
*
* @param bundles
* Bundles which exported packages are to be refreshed.
*/
private void refreshPackages(Bundle[] bundles) throws CoreException {
ServiceReference packageAdminReference = _context.getServiceReference(PackageAdmin.class.getName());
PackageAdmin packageAdmin = null;
if (packageAdminReference != null) {
packageAdmin = (PackageAdmin) _context.getService(packageAdminReference);
}
if (packageAdmin == null) {
return;
}
// State for thread synchronization
final boolean[] framework = new boolean[] {
false
};
// Storage for thread framework exception
final Throwable[] throwable = new Throwable[1];
// Listener
FrameworkListener listener = new FrameworkListener() {
public void frameworkEvent(FrameworkEvent event) {
if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED || event.getType() == FrameworkEvent.ERROR) {
if (event.getType() == FrameworkEvent.ERROR) {
throwable[0] = event.getThrowable();
}
synchronized (framework) {
framework[0] = true;
framework.notifyAll();
}
} else if (event.getType() == FrameworkEvent.WARNING) {
if (event.getThrowable() != null) {
EGFCorePlugin.getDefault().logWarning(event.getThrowable());
}
}
}
};
_context.addFrameworkListener(listener);
// Refresh packages
packageAdmin.refreshPackages(bundles);
synchronized (framework) {
while (framework[0] == false) {
try {
framework.wait();
} catch (InterruptedException e) {
break;
}
}
}
_context.removeFrameworkListener(listener);
_context.ungetService(packageAdminReference);
// Throw a CoreException
if (throwable[0] != null) {
throw new CoreException(EGFCorePlugin.getDefault().newStatus(IStatus.ERROR, EGFCoreMessages.ProjectBundleSession_PackageRefreshFailure, throwable[0]));
}
}
/**
* This can be used to uninstall all manually loaded bundles from the
* registry
* and remove all listeners.
* It will be called on plugin stopping and is not intended to be called by
* clients.
*
* @noreference This method is not intended to be referenced by clients.
*/
public void dispose() throws CoreException {
// shallow copy our bundle collector
final List<Bundle> uninstalledBundles = new UniqueEList<Bundle>(_uninstalled.size());
// Uninstall workspace bundle
if (_projectBundles.isEmpty() == false || _uninstalled.isEmpty() == false) {
// Trace
if (EGFCoreDebug.isDebugBundleSession()) {
if (_projectBundles.isEmpty() == false || _uninstalled.isEmpty() == false) {
EGFCorePlugin.getDefault().logInfo("Dispose ProjectBundleSession..."); //$NON-NLS-1$
}
}
// Uninstall workspace bundle
if (_projectBundles.isEmpty() == false) {
for (Bundle bundle : _projectBundles.values()) {
uninstallBundle(bundle);
}
// Refresh Packages
refreshPackages(_projectBundles.values().toArray(new Bundle[_projectBundles.size()]));
// Tracing
if (EGFCoreDebug.isDebugBundleSession()) {
if (_projectBundles.isEmpty() == false) {
for (Bundle bundle : _projectBundles.values()) {
EGFCorePlugin.getDefault().logInfo(NLS.bind("Workspace Bundle ''{0}'' is uninstalled.", bundle.getSymbolicName()), 1); //$NON-NLS-1$
}
}
}
}
// Install runtime bundle
if (_uninstalled.isEmpty() == false) {
for (String location : _uninstalled) {
uninstalledBundles.add(installBundle(location));
}
// Refresh Packages
refreshPackages(uninstalledBundles.toArray(new Bundle[uninstalledBundles.size()]));
// Tracing
if (EGFCoreDebug.isDebugBundleSession()) {
if (_uninstalled.isEmpty() == false) {
for (Bundle bundle : uninstalledBundles) {
EGFCorePlugin.getDefault().logInfo(NLS.bind("Runtime Bundle ''{0}'' is installed.", bundle.getSymbolicName()), 1); //$NON-NLS-1$
}
}
}
}
}
// Final
_projectBundles.clear();
_uninstalled.clear();
}
/**
* Uninstall the given bundle from the context.
*
* @param bundle
* The bundle that is to be uninstalled.
* @throws CoreException
* Thrown if a lifecycle issue arises.
*/
private void uninstallBundle(Bundle bundle) throws CoreException {
try {
bundle.uninstall();
} catch (BundleException be) {
throw new CoreException(EGFCorePlugin.getDefault().newStatus(IStatus.ERROR, NLS.bind(EGFCoreMessages.ProjectBundleSession_UninstallationFailure, bundle.getSymbolicName()), be));
}
}
// TODO: not sure if we still need this support while we have now runtime and target platform support
private boolean isRuntimeBundlePriority() {
if (runtimeBundlePriority == null) {
String property = System.getProperty(EGF_TARGET_BUNDLE_PRIORITY);
runtimeBundlePriority = Boolean.TRUE.toString().equals(property);
}
return runtimeBundlePriority;
}
}