blob: e8b63be2015cc1927c9eb5fac5439d660807d7eb [file] [log] [blame]
/**
* Copyright (c) 2008 IBM Corporation and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*/
package org.eclipse.egf.core.session;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
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.common.helper.ObjectHolder;
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.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
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);
this.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 = doInstallBundle(workspaceModel);
// Store
bundles.add(bundle);
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);
final String location = getLocation(workspaceModel);
if (workspaceBundleId != null && !projectBundles.containsKey(location)) {
registryContributors.add(workspaceBundleId);
}
}
}
final ObjectHolder<Boolean> loadCompleted = new ObjectHolder<Boolean>();
// build a listener to wait for asynchronous extensions notifications
IRegistryEventListener registryListener = null;
if (!registryContributors.isEmpty()) {
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()) {
loadCompleted.object = Boolean.TRUE;
}
}
}
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) {
int timeout = 20;
try {
while (loadCompleted.object == null && timeout-- > 0)
Thread.sleep(100);
} catch (InterruptedException e) {
} finally {
// Unregister
RegistryFactory.getRegistry().removeListener(registryListener);
}
if (timeout < 0)
EGFCorePlugin.getDefault().logWarning("Some bundles (related to " + bundleId + ") may not be fully loaded.");
}
// 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
installWorkspaceModels(bundles, workspaceModels);
// Refresh installed workspace bundles if any
if (!bundles.isEmpty()) {
refreshPackages(bundles.toArray(new Bundle[bundles.size()]));
}
// Start them
for (Bundle bundle : bundles) {
try {
if (bundle.getState() != Bundle.ACTIVE) {
bundle.start(Bundle.START_TRANSIENT);
}
} 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);
}
// 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;
}
private void addContent(File folder, String path, String zipPath, List<String> outputFolders, ZipOutputStream out, Set<String> names) throws IOException {
int BUFFER = 1024;
byte data[] = new byte[BUFFER];
String subdirList[] = folder.list();
for (String sd : subdirList) {
File f = new File(folder, sd);
String subPath = path.equals("") ? sd : path + "/" + sd;
String subzipPath = zipPath.equals("") ? sd : zipPath + "/" + sd;
if (f.isDirectory()) {
if (outputFolders.contains(sd))
subzipPath = zipPath;
addContent(f, subPath, subzipPath, outputFolders, out, names);
} else if (!names.contains(subzipPath))
{
BufferedInputStream origin = new BufferedInputStream(new FileInputStream(f));
ZipEntry entry = new ZipEntry(subzipPath);
names.add(subzipPath);
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
out.closeEntry();
origin.close();
}
}
}
/**
* 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 doInstallBundle(IPluginModelBase workspaceModel) throws CoreException {
Bundle bundle = null;
String location = getLocation(workspaceModel);
try {
ByteArrayOutputStream array = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(array);
IProject project = workspaceModel.getUnderlyingResource().getProject();
if (project.hasNature(JavaCore.NATURE_ID))
{
IJavaProject jProject = JavaCore.create(project);
List<String> outputFolders = JavaHelper.getStringOutputFolders(jProject);
Set<String> names = new HashSet<String>();
addContent(new File(project.getLocation().toPortableString()), "", "", outputFolders, out, names);
out.close();
array.flush();
array.close();
bundle = context.installBundle("ID->" + location, new ByteArrayInputStream(array.toByteArray()));
}
else
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;
}
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;
}
/**
* 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;
}
final ObjectHolder<Throwable> thrown = new ObjectHolder<Throwable>();
final ObjectHolder<Boolean> loadCompleted = new ObjectHolder<Boolean>();
// 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) {
thrown.object = event.getThrowable();
}
loadCompleted.object = Boolean.TRUE;
} else if (event.getType() == FrameworkEvent.WARNING) {
if (event.getThrowable() != null) {
EGFCorePlugin.getDefault().logWarning(event.getThrowable());
}
}
}
};
context.addFrameworkListener(listener);
// Refresh packages
packageAdmin.refreshPackages(bundles);
int timeout = 20;
try {
while (loadCompleted.object == null && timeout-- > 0)
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (timeout < 0)
EGFCorePlugin.getDefault().logWarning("Some bundles may not be fully loaded.");
context.removeFrameworkListener(listener);
context.ungetService(packageAdminReference);
// Throw a CoreException
if (thrown.object != null) {
throw new CoreException(EGFCorePlugin.getDefault().newStatus(IStatus.ERROR, EGFCoreMessages.ProjectBundleSession_PackageRefreshFailure, thrown.object));
}
}
/**
* 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;
}
}