/*******************************************************************************
* Copyright (c) 2014, 2015 vogella GmbH 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:
* Simon Scholz <scholzsimon@vogella.com> - Bug 445663
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 445663
*******************************************************************************/
package org.eclipse.ui.internal.ide.application.addons;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.di.extensions.EventTopic;
import org.eclipse.e4.core.services.log.Logger;
import org.eclipse.e4.ui.internal.workbench.URIHelper;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.descriptor.basic.MPartDescriptor;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.e4.ui.workbench.UIEvents.UILifeCycle;
import org.eclipse.ui.internal.registry.ViewRegistry;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.resource.Namespace;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.service.event.Event;
/**
* The model-addon searches for model contributions in the runtime application
* and removes elements for which the classes cannot be accessed anymore.
* Currently it only covered part descriptors but it is planned to extend this
* addon to also remove other broken model contributions
*/
@SuppressWarnings("restriction")
public class ModelCleanupAddon {
/**
* See URIHelper#BUNDLECLASS_SCHEMA constant.
*/
private static final int BUNDLECLASS_SCHEMA_LENGTH = 14;
private static String COMPATIBILITY_EDITOR_URI = "bundleclass://org.eclipse.ui.workbench/org.eclipse.ui.internal.e4.compatibility.CompatibilityEditor"; //$NON-NLS-1$
private static String COMPATIBILITY_VIEW_URI = "bundleclass://org.eclipse.ui.workbench/org.eclipse.ui.internal.e4.compatibility.CompatibilityView"; //$NON-NLS-1$
@Inject
@Optional
private MApplication application;
@Inject
@Optional
private Logger logger;
/**
* This addon listens to the {@link UILifeCycle#APP_STARTUP_COMPLETE} event.
*
* @param event
* {@link Event}
*/
@Inject
@Optional
public void applicationStartUp(@EventTopic(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE) Event event) {
List<MPartDescriptor> descriptors = application.getDescriptors();
Bundle bundle = FrameworkUtil.getBundle(getClass());
for (Iterator<MPartDescriptor> iterator = descriptors.iterator(); iterator.hasNext();) {
MPartDescriptor partDescriptor = iterator.next();
boolean validPartDescriptor = isValidPartDescriptor(bundle, partDescriptor);
if (!validPartDescriptor) {
logger.warn("Removing part descriptor with the '" + partDescriptor.getElementId() //$NON-NLS-1$
+ "' id and the '" + partDescriptor.getLocalizedLabel() //$NON-NLS-1$
+ "' description. Points to the invalid '" + partDescriptor.getContributionURI() + "' class."); //$NON-NLS-1$ //$NON-NLS-2$
iterator.remove();
}
}
}
private boolean isValidPartDescriptor(Bundle bundle, MPartDescriptor partDescriptor) {
String contributionURI = partDescriptor.getContributionURI();
if (!URIHelper.isBundleClassUri(contributionURI)) {
return false;
}
String originalCompatibilityViewClass = partDescriptor.getPersistedState()
.get(ViewRegistry.ORIGINAL_COMPATIBILITY_VIEW_CLASS);
// if the originalCompatibilityViewClass is not null, the given
// MPartDescriptor is based on a ViewPart (not e4view)
// See createDescriptor method of the ViewRegistry
if (COMPATIBILITY_VIEW_URI.equals(contributionURI) && originalCompatibilityViewClass != null) {
String originalCompatibilityViewBundle = partDescriptor.getPersistedState()
.get(ViewRegistry.ORIGINAL_COMPATIBILITY_VIEW_BUNDLE);
return checkPartDescriptorByBundleSymbolicNameAndClass(bundle, originalCompatibilityViewBundle,
originalCompatibilityViewClass);
} else if (!COMPATIBILITY_EDITOR_URI.equals(contributionURI)) {
// check for e4views and usual MPartDescriptors
String[] bundleClass = contributionURI.substring(BUNDLECLASS_SCHEMA_LENGTH).split("/"); //$NON-NLS-1$
String bundleSymbolicName = bundleClass[0];
String className = bundleClass[1];
return checkPartDescriptorByBundleSymbolicNameAndClass(bundle, bundleSymbolicName, className);
}
return true;
}
private boolean checkPartDescriptorByBundleSymbolicNameAndClass(Bundle bundle, String bundleSymbolicName,
String className) {
Collection<BundleWiring> wirings = findWirings(bundleSymbolicName, bundle.getBundleContext());
if (!isPartDescriptorClassAvailable(wirings, className)) {
// remove PartDescriptor, if there is not wiring available
// or if the class cannot be found
return false;
}
return true;
}
private boolean isPartDescriptorClassAvailable(Collection<BundleWiring> wirings, String className) {
if (wirings.isEmpty()) {
return false;
}
String classPackageName;
String classResourceName;
int indexLastDot = className.lastIndexOf('.');
if (indexLastDot < 0) {
classPackageName = "/"; //$NON-NLS-1$
classResourceName = className;
} else {
classPackageName = '/' + className.substring(0, indexLastDot).replace('.', '/');
classResourceName = className.substring(indexLastDot + 1) + ".class"; //$NON-NLS-1$
}
for (BundleWiring bundleWiring : wirings) {
if (!checkClassResource(classPackageName, classResourceName, bundleWiring)) {
return false;
}
}
return true;
}
private Collection<BundleWiring> findWirings(final String bundleSymbolicName, BundleContext bundleContext) {
Requirement req = new Requirement() {
@Override
public Resource getResource() {
// no resource
return null;
}
@Override
public String getNamespace() {
return IdentityNamespace.IDENTITY_NAMESPACE;
}
@Override
public Map<String, String> getDirectives() {
return Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE,
"(" + IdentityNamespace.IDENTITY_NAMESPACE + "=" + bundleSymbolicName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
@Override
public Map<String, Object> getAttributes() {
return Collections.emptyMap();
}
};
Collection<BundleCapability> identities = bundleContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION)
.adapt(FrameworkWiring.class).findProviders(req);
Collection<BundleWiring> result = new ArrayList<BundleWiring>(1); // normally
// only
// one
for (BundleCapability identity : identities) {
BundleRevision revision = identity.getRevision();
BundleWiring wiring = revision.getWiring();
if (wiring != null) {
if ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
// fragment case; need to get the host wiring
wiring = wiring.getRequiredWires(HostNamespace.HOST_NAMESPACE).get(0).getProviderWiring();
}
result.add(wiring);
}
}
return result;
}
private boolean checkClassResource(String classPackageName, String classFileName, BundleWiring wiring) {
if (wiring == null) {
return false;
}
if ((wiring.getRevision().getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
// fragment case; need to get the host wiring
wiring = wiring.getRequiredWires(HostNamespace.HOST_NAMESPACE).get(0).getProviderWiring();
}
Collection<String> classResourcePaths = wiring.listResources(classPackageName, classFileName, 0);
return classResourcePaths != null && !classResourcePaths.isEmpty();
}
}