/** | |
* 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.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.IStatus; | |
import org.eclipse.core.runtime.Platform; | |
import org.eclipse.egf.common.helper.BundleHelper; | |
import org.eclipse.egf.common.helper.JavaHelper; | |
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 { | |
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>(); | |
public static String getLocation(IPluginModelBase base) throws CoreException { | |
IResource resource = base.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; | |
} | |
/** | |
* Installs the bundle corresponding to the model. | |
* | |
* @param model | |
* Model of the bundle to be installed. | |
*/ | |
private Bundle installBundle(IPluginModelBase base) throws CoreException { | |
// In case we face a target bundle, we do nothing | |
if (getLocation(base) == null) { | |
return null; | |
} | |
// Gather target bundles to uninstall including base | |
List<IPluginModelBase> workspaceModels = getWorkspaceModelDependencies(base); | |
// Uninstall target bundles if any | |
uninstallTargetBundle(workspaceModels); | |
// Install workspace bundle | |
List<Bundle> bundles = new UniqueEList<Bundle>(); | |
for (IPluginModelBase workspaceModel : workspaceModels) { | |
// 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); | |
_projectBundles.put(location, bundle); | |
if (EGFCorePlugin.getDefault().isDebugging()) { | |
EGFCorePlugin.getDefault().logInfo(NLS.bind("Workspace Bundle ''{0}'' is installed.", bundle.getSymbolicName())); //$NON-NLS-1$ | |
} | |
} | |
// Refresh installed workspace bundles if any | |
if (bundles.isEmpty() == false) { | |
refreshPackages(bundles.toArray(new Bundle[bundles.size()])); | |
} | |
// Return our base bundle | |
return Platform.getBundle(BundleHelper.getBundleId(base)); | |
} | |
private void uninstallTargetBundle(List<IPluginModelBase> workspaceModels) throws CoreException { | |
List<Bundle> bundles = new UniqueEList<Bundle>(); | |
// Uninstall Target Bundle | |
for (IPluginModelBase workspaceModel : workspaceModels) { | |
Bundle bundle = Platform.getBundle(BundleHelper.getBundleId(workspaceModel)); | |
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 | |
bundles.add(bundle); | |
_uninstalled.add(bundle.getLocation()); | |
if (EGFCorePlugin.getDefault().isDebugging()) { | |
EGFCorePlugin.getDefault().logInfo(NLS.bind("Target Bundle ''{0}'' is uninstalled.", bundle.getSymbolicName())); //$NON-NLS-1$ | |
} | |
} | |
// Refresh uninstalled target bundles if any | |
if (bundles.isEmpty() == false) { | |
refreshPackages(bundles.toArray(new Bundle[bundles.size()])); | |
} | |
return; | |
} | |
private List<IPluginModelBase> getWorkspaceModelDependencies(IPluginModelBase base) throws CoreException { | |
List<IPluginModelBase> dependencies = new UniqueEList<IPluginModelBase>(); | |
dependencies.add(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; | |
} | |
} | |
} | |
} | |
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 project | |
* 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 { | |
IPluginModelBase model = PluginRegistry.findModel(id); | |
if (model == null) { | |
return null; | |
} | |
// Check if we face a non workspace model | |
if (model.getUnderlyingResource() == null) { | |
return Platform.getBundle(BundleHelper.getBundleId(model)); | |
} | |
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 { | |
IPluginModelBase model = PluginRegistry.findModel(project); | |
if (model == null) { | |
return null; | |
} | |
String location = getLocation(model); | |
if (location == null) { | |
return null; | |
} | |
Bundle bundle = _projectBundles.get(location); | |
if (bundle == null) { | |
return 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) { | |
final boolean[] flag = new boolean[] { false }; | |
final Throwable[] throwable = new Throwable[1]; | |
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 (flag) { | |
flag[0] = true; | |
flag.notifyAll(); | |
} | |
} else if (event.getType() == FrameworkEvent.WARNING) { | |
if (event.getThrowable() != null) { | |
EGFCorePlugin.getDefault().logWarning(event.getThrowable()); | |
} | |
} | |
} | |
}; | |
_context.addFrameworkListener(listener); | |
packageAdmin.refreshPackages(bundles); | |
synchronized (flag) { | |
while (flag[0] == false) { | |
try { | |
flag.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 { | |
// Reinstall bundle collector | |
final List<Bundle> bundles = new UniqueEList<Bundle>(_uninstalled.size()); | |
// Uninstall workspace bundle | |
if (_projectBundles.isEmpty() == false) { | |
for (Bundle bundle : _projectBundles.values()) { | |
uninstallBundle(bundle); | |
if (EGFCorePlugin.getDefault().isDebugging()) { | |
EGFCorePlugin.getDefault().logInfo(NLS.bind("Workspace Bundle ''{0}'' is uninstalled.", bundle.getSymbolicName())); //$NON-NLS-1$ | |
} | |
} | |
refreshPackages(_projectBundles.values().toArray(new Bundle[_projectBundles.values().size()])); | |
} | |
// Install target bundles | |
if (_uninstalled.isEmpty() == false) { | |
for (String location : _uninstalled) { | |
Bundle bundle = installBundle(location); | |
bundles.add(bundle); | |
if (EGFCorePlugin.getDefault().isDebugging()) { | |
EGFCorePlugin.getDefault().logInfo(NLS.bind("Target Bundle ''{0}'' is installed.", bundle.getSymbolicName())); //$NON-NLS-1$ | |
} | |
} | |
refreshPackages(bundles.toArray(new Bundle[bundles.size()])); | |
} | |
// Final | |
_projectBundles.clear(); | |
_uninstalled.clear(); | |
} | |
/** | |
* Uninstalls 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)); | |
} | |
} | |
} |