Bug 571866 - Add possibility to register fragments and processors
via DS
Change-Id: I16c4054174945273f421c008e48505dd47ebfaa8
Signed-off-by: Dirk Fauth <dirk.fauth@googlemail.com>
diff --git a/bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF
index 93ccc50..15115d0 100644
--- a/bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.eclipse.e4.ui.workbench;singleton:=true
-Bundle-Version: 1.12.100.qualifier
+Bundle-Version: 1.13.0.qualifier
Bundle-Name: %pluginName
Bundle-Vendor: %providerName
Bundle-Localization: plugin
@@ -41,7 +41,8 @@
Bundle-Activator: org.eclipse.e4.ui.internal.workbench.Activator
Service-Component: OSGI-INF/org.eclipse.e4.ui.internal.LocaleChangeServiceContextFunction.xml,
OSGI-INF/org.eclipse.e4.ui.internal.workbench.PartServiceCreationFunction.xml,
- OSGI-INF/org.eclipse.e4.ui.internal.workbench.ProgressMonitorFunction.xml
+ OSGI-INF/org.eclipse.e4.ui.internal.workbench.ProgressMonitorFunction.xml,
+ OSGI-INF/org.eclipse.e4.ui.internal.workbench.ModelAssembler.xml
Import-Package: javax.annotation,
javax.inject;version="1.0.0",
org.apache.commons.jxpath;version="1.3.0",
diff --git a/bundles/org.eclipse.e4.ui.workbench/OSGI-INF/org.eclipse.e4.ui.internal.workbench.ModelAssembler.xml b/bundles/org.eclipse.e4.ui.workbench/OSGI-INF/org.eclipse.e4.ui.internal.workbench.ModelAssembler.xml
new file mode 100644
index 0000000..80ba725
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.workbench/OSGI-INF/org.eclipse.e4.ui.internal.workbench.ModelAssembler.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="activate" deactivate="deactivate" immediate="true" name="org.eclipse.e4.ui.internal.workbench.ModelAssembler">
+ <service>
+ <provide interface="org.eclipse.e4.ui.internal.workbench.ModelAssembler"/>
+ </service>
+ <reference bind="setExtensionRegistry" interface="org.eclipse.core.runtime.IExtensionRegistry" name="ExtensionRegistry"/>
+ <reference bind="setLogger" cardinality="0..1" interface="org.osgi.service.log.LoggerFactory" name="Logger" policy="dynamic" unbind="unsetLogger"/>
+ <reference bind="setModelProcessorContribution" cardinality="0..n" interface="org.eclipse.e4.ui.workbench.modeling.IModelProcessorContribution" name="ModelProcessorContribution" policy="dynamic" unbind="unsetModelProcessorContribution"/>
+ <implementation class="org.eclipse.e4.ui.internal.workbench.ModelAssembler"/>
+</scr:component>
\ No newline at end of file
diff --git a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ModelAssembler.java b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ModelAssembler.java
index 744c26e..239b660 100644
--- a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ModelAssembler.java
+++ b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ModelAssembler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2010, 2020 BestSolution.at and others.
+ * Copyright (c) 2010, 2021 BestSolution.at and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -22,6 +22,7 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Dictionary;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -31,9 +32,11 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
-import javax.inject.Inject;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
import org.eclipse.core.runtime.IConfigurationElement;
-import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
@@ -42,14 +45,17 @@
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.core.services.contributions.IContributionFactory;
-import org.eclipse.e4.core.services.log.Logger;
+import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.MApplicationElement;
+import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.fragment.MModelFragment;
import org.eclipse.e4.ui.model.fragment.MModelFragments;
import org.eclipse.e4.ui.model.fragment.MStringModelFragment;
import org.eclipse.e4.ui.model.fragment.impl.FragmentPackageImpl;
import org.eclipse.e4.ui.model.internal.ModelUtils;
+import org.eclipse.e4.ui.workbench.modeling.IModelProcessorContribution;
+import org.eclipse.e4.ui.workbench.modeling.IModelProcessorContribution.ModelElement;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
@@ -61,12 +67,29 @@
import org.eclipse.emf.ecore.util.Diagnostician;
import org.eclipse.emf.ecore.util.EContentsEList;
import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
/**
* The ModelAssembler is responsible for adding {@link MModelFragment fragments}
* and {@link MApplicationElement} imports to the application model and running
* pre- and post-processors on the model.
*/
+@Component(service = ModelAssembler.class, immediate = true)
public class ModelAssembler {
private class Bucket {
@@ -76,23 +99,200 @@
Set<String> containedElementIds = new LinkedHashSet<>();
}
- @Inject
- private Logger logger;
+ private class FragmentWrapperElementMapping {
+ ModelFragmentWrapper wrapper;
+ List<MApplicationElement> elements;
+ }
- @Inject
+ private class ModelFragmentBundleTracker implements BundleTrackerCustomizer<List<FragmentWrapperElementMapping>> {
+
+ @Override
+ public List<FragmentWrapperElementMapping> addingBundle(Bundle bundle, BundleEvent event) {
+ // only react on bundles with Model-Fragment header
+ if (bundle.getHeaders("").get(MODEL_FRAGMENT_HEADER) != null) { //$NON-NLS-1$
+ // add the fragment to the application model
+ List<ModelFragmentWrapper> wrappers = getModelFragmentWrapperFromBundle(bundle,
+ ModelAssembler.this.initial);
+
+ List<FragmentWrapperElementMapping> mappings = wrappers.stream().map(w -> {
+ FragmentWrapperElementMapping mapping = new FragmentWrapperElementMapping();
+ mapping.wrapper = w;
+ mapping.elements = new ArrayList<>(w.getModelFragment().getElements());
+ return mapping;
+ }).collect(Collectors.toList());
+
+ // we skip direct processing in case the startup model processing is not done
+ // yet
+ if (processModelExecuted) {
+ uiSync.asyncExec(() -> processFragmentWrappers(wrappers));
+ }
+
+ return mappings;
+ }
+ return null;
+ }
+
+ @Override
+ public void modifiedBundle(Bundle bundle, BundleEvent event, List<FragmentWrapperElementMapping> mapping) {
+ // do nothing
+ }
+
+ @Override
+ public void removedBundle(Bundle bundle, BundleEvent event, List<FragmentWrapperElementMapping> mappings) {
+ // remove fragment elements from application model
+ uiSync.asyncExec(() -> {
+ if (mappings != null) {
+ mappings.stream().flatMap(m -> m.elements.stream()).forEach(appElement -> {
+ // TODO implement removal of contributions, e.g. MenuContributions
+
+ if (appElement instanceof MUIElement) {
+ MUIElement element = (MUIElement) appElement;
+ element.setToBeRendered(false);
+ if (element.getParent() != null) {
+ element.getParent().getChildren().remove(element);
+ }
+ }
+ });
+
+ // unload resource
+ String bundleName = bundle.getSymbolicName();
+ String fragmentHeader = bundle.getHeaders("").get(MODEL_FRAGMENT_HEADER); //$NON-NLS-1$
+ String[] fr = fragmentHeader.split(";"); //$NON-NLS-1$
+ if (fr.length > 0) {
+ String attrURI = fr[0];
+ E4XMIResource applicationResource = (E4XMIResource) ((EObject) application).eResource();
+ ResourceSet resourceSet = applicationResource.getResourceSet();
+ if (attrURI == null) {
+ log(LogLevel.WARN, "Unable to find location for the model extension \"{0}\"", bundleName); //$NON-NLS-1$
+ return;
+ }
+
+ URI uri;
+ try {
+ // check if the attrURI is already a platform URI
+ if (URIHelper.isPlatformURI(attrURI)) {
+ uri = URI.createURI(attrURI);
+ } else {
+ String path = bundleName + '/' + attrURI;
+ uri = URI.createPlatformPluginURI(path, false);
+ }
+ } catch (RuntimeException e) {
+ log(LogLevel.WARN, "Invalid location \"{0}\" of model extension \"\"", attrURI, bundleName, //$NON-NLS-1$
+ e);
+ return;
+ }
+
+ try {
+ Resource resource = resourceSet.getResource(uri, true);
+ resource.unload();
+ } catch (RuntimeException e) {
+ log(LogLevel.WARN, "Unable to read model extension from \"{0}\" of \"{1}\"", uri, //$NON-NLS-1$
+ bundleName);
+ }
+ }
+
+ }
+ });
+ }
+ }
+
private MApplication application;
-
- @Inject
private IEclipseContext context;
-
- @Inject
- private IExtensionRegistry registry;
+ private UISynchronize uiSync;
+ private boolean initial;
private static final String EXTENSION_POINT_ID = "org.eclipse.e4.workbench.model"; //$NON-NLS-1$
+ private static final String MODEL_FRAGMENT_HEADER = "Model-Fragment"; //$NON-NLS-1$
+ private static final String ALWAYS = "always"; //$NON-NLS-1$
private static final String INITIAL = "initial"; //$NON-NLS-1$
private static final String NOTEXISTS = "notexists"; //$NON-NLS-1$
+ LoggerFactory factory;
+ Logger logger;
+
+ private IExtensionRegistry registry;
+
+ private CopyOnWriteArrayList<ServiceReference<IModelProcessorContribution>> processorContributions = new CopyOnWriteArrayList<>();
+
+ BundleContext bundleContext;
+
+ BundleTracker<List<FragmentWrapperElementMapping>> tracker;
+
+ private boolean processModelExecuted = false;
+
+ @Activate
+ void activate(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+
+ this.tracker = new BundleTracker<>(bundleContext,
+ Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING, new ModelFragmentBundleTracker());
+ }
+
+ @Deactivate
+ void deactivate() {
+ if (this.tracker != null) {
+ this.tracker.close();
+ }
+ }
+
+ /**
+ * Setter for the {@link IExtensionRegistry}. Use public method binding instead
+ * of field binding to enable testing.
+ *
+ * @param registry The {@link IExtensionRegistry} to use.
+ */
+ @Reference
+ public void setExtensionRegistry(IExtensionRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+ void setModelProcessorContribution(ServiceReference<IModelProcessorContribution> contrib) {
+ this.processorContributions.add(contrib);
+
+ // we skip direct processing in case the startup model processing is not done
+ // yet
+ if (processModelExecuted) {
+ uiSync.asyncExec(() -> {
+ IModelProcessorContribution service = bundleContext.getService(contrib);
+ runProcessor(service);
+ });
+ }
+ }
+
+ void unsetModelProcessorContribution(ServiceReference<IModelProcessorContribution> contrib) {
+ this.processorContributions.remove(contrib);
+ IModelProcessorContribution service = bundleContext.getService(contrib);
+
+ if (this.context != null) {
+ uiSync.asyncExec(() -> {
+ try {
+ ContextInjectionFactory.invoke(service, PreDestroy.class, context);
+ } catch (Exception e) {
+ log(LogLevel.WARN, "Could not run PreDestroy on processor {0}: {1}", contrib.getClass().getName(), //$NON-NLS-1$
+ e);
+ }
+ });
+ }
+ }
+
+ /**
+ * Initialize this {@link ModelAssembler} with references from the Eclipse
+ * layer. Used via CIF#invoke instead of CIF#inject or CIF#make to avoid that
+ * this instance is managed by the Eclipse injection.
+ *
+ * @param application the application model
+ * @param context the workbench context needed for injection
+ * @param sync to dynamically add/remove UI elements in the UI thread
+ */
+ @PostConstruct
+ public void init(MApplication application, IEclipseContext context, UISynchronize sync) {
+ this.application = application;
+ this.context = context;
+ this.uiSync = sync;
+ }
+
/**
* Processes the application model. This will run pre-processors, process the
* fragments, resolve imports and run post-processors, in this order. <br>
@@ -101,9 +301,12 @@
* Extension points will be sorted based on the dependencies of their
* contributors.
*
- * @param initial <code>true</code> if running from a non-persisted state
+ * @param initial <code>true</code> if running from a non-persisted state
*/
+ @Execute
public void processModel(boolean initial) {
+ this.initial = initial;
+
IExtensionPoint extPoint = registry.getExtensionPoint(EXTENSION_POINT_ID);
IExtension[] extensions = new ExtensionsSort().sort(extPoint.getExtensions());
@@ -113,6 +316,11 @@
processFragments(extensions, initial);
// run processors which are marked to run after fragments
runProcessors(extensions, initial, true);
+
+ // once we are done, any further handling in the tracker can't be initial
+ // anymore
+ this.initial = false;
+ this.processModelExecuted = true;
}
/**
@@ -129,7 +337,8 @@
IConfigurationElement[] ces = extension.getConfigurationElements();
for (IConfigurationElement ce : ces) {
if ("fragment".equals(ce.getName()) && (initial || !INITIAL.equals(ce.getAttribute("apply")))) { //$NON-NLS-1$ //$NON-NLS-2$
- MModelFragments fragmentsContainer = getFragmentsContainer(ce);
+ MModelFragments fragmentsContainer = getFragmentsContainer(ce.getAttribute("uri"), //$NON-NLS-1$
+ ce.getContributor().getName());
if (fragmentsContainer == null) {
continue;
}
@@ -143,9 +352,57 @@
}
}
+ if (this.tracker != null) {
+ // this triggers initial bundle tracking in the current thread
+ // for startup reasons we do not process each fragment on initial tracking by
+ // its own, instead we will process the initially tracked bundles together once
+ // the initial tracking is done
+ this.tracker.open();
+
+ // once the initial tracking is done we process the tracked bundles
+ // this is for performance optimization on initial loading to avoid multiple
+ // fragment merge operations
+ List<ModelFragmentWrapper> collect = this.tracker.getTracked().values().stream()
+ .flatMap(List::stream).map(w -> w.wrapper).collect(Collectors.toList());
+ wrappers.addAll(collect);
+ }
+
processFragmentWrappers(wrappers);
}
+ private List<ModelFragmentWrapper> getModelFragmentWrapperFromBundle(Bundle bundle, boolean initial) {
+ List<ModelFragmentWrapper> wrappers = new ArrayList<>();
+ String fragmentHeader = bundle.getHeaders("").get(MODEL_FRAGMENT_HEADER); //$NON-NLS-1$
+ String[] fr = fragmentHeader.split(";"); //$NON-NLS-1$
+ if (fr.length > 0) {
+ String uri = fr[0];
+ String apply = fr.length > 1 ? fr[1].split("=")[1] : "always"; //$NON-NLS-1$ //$NON-NLS-2$
+
+ // check if the value for apply is valid
+ if (!ALWAYS.equals(apply) && !INITIAL.equals(apply) && !NOTEXISTS.equals(apply)) {
+ log(LogLevel.WARN, "Model-Fragment header apply attribute \"{0}\" is invalid, falling back to always", //$NON-NLS-1$
+ apply);
+ apply = ALWAYS;
+ }
+
+ if (initial || !INITIAL.equals(apply)) {
+ MModelFragments fragmentsContainer = getFragmentsContainer(uri, bundle.getSymbolicName());
+ if (fragmentsContainer != null) {
+ for (MModelFragment fragment : fragmentsContainer.getFragments()) {
+ boolean checkExist = !initial && NOTEXISTS.equals(apply);
+ wrappers.add(new ModelFragmentWrapper(fragmentsContainer, fragment, bundle.getSymbolicName(),
+ URIHelper.constructPlatformURI(bundle), checkExist)); // $NON-NLS-1$
+ }
+ }
+ }
+ } else {
+ log(LogLevel.ERROR, "Model-Fragment header value \"{0}\" in bundle \\\"{1}\\\" is invalid", //$NON-NLS-1$
+ fragmentHeader, bundle.getSymbolicName());
+ }
+
+ return wrappers;
+ }
+
/**
* Processes the given list of fragments wrapped in {@link ModelFragmentWrapper}
* elements.
@@ -248,7 +505,7 @@
Diagnostic validationResult = Diagnostician.INSTANCE.validate((EObject) fragment);
int severity = validationResult.getSeverity();
if (severity == Diagnostic.ERROR) {
- logger.error(
+ log(LogLevel.ERROR,
"Fragment from \"{0}\" of \"{1}\" could not be validated and was not merged: " //$NON-NLS-1$
+ fragment, contributorURI, contributorName);
}
@@ -258,7 +515,7 @@
evalImports = true;
addedElements.addAll(merged);
} else {
- logger.debug("Nothing to merge for fragment \"{0}\" of \"{1}\"", contributorURI, //$NON-NLS-1$
+ log(LogLevel.DEBUG, "Nothing to merge for fragment \"{0}\" of \"{1}\"", contributorURI, //$NON-NLS-1$
contributorName);
}
if (evalImports && fragmentsContainer.getImports().size() > 0) {
@@ -266,14 +523,11 @@
}
}
- private MModelFragments getFragmentsContainer(IConfigurationElement ce) {
+ private MModelFragments getFragmentsContainer(String attrURI, String bundleName) {
E4XMIResource applicationResource = (E4XMIResource) ((EObject) application).eResource();
ResourceSet resourceSet = applicationResource.getResourceSet();
- IContributor contributor = ce.getContributor();
- String attrURI = ce.getAttribute("uri"); //$NON-NLS-1$
- String bundleName = contributor.getName();
if (attrURI == null) {
- logger.warn("Unable to find location for the model extension \"{0}\"", bundleName); //$NON-NLS-1$
+ log(LogLevel.WARN, "Unable to find location for the model extension \"{0}\"", bundleName); //$NON-NLS-1$
return null;
}
@@ -287,7 +541,7 @@
uri = URI.createPlatformPluginURI(path, false);
}
} catch (RuntimeException e) {
- logger.warn(e, "Invalid location \"" + attrURI + "\" of model extension \"" + bundleName + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ log(LogLevel.WARN, "Invalid location \"{0}\" of model extension \"\"", attrURI, bundleName, e); //$NON-NLS-1$
return null;
}
@@ -295,7 +549,7 @@
try {
resource = resourceSet.getResource(uri, true);
} catch (RuntimeException e) {
- logger.warn(e, "Unable to read model extension from \"" + uri + "\" of \"" + bundleName + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ log(LogLevel.WARN, "Unable to read model extension from \"{0}\" of \"{1}\"", uri, bundleName); //$NON-NLS-1$
return null;
}
@@ -307,7 +561,7 @@
Object extensionRoot = contents.get(0);
if (!(extensionRoot instanceof MModelFragments)) {
- logger.warn("Unable to create model extension \"{0}\"", bundleName); //$NON-NLS-1$
+ log(LogLevel.WARN, "Unable to create model extension \"{0}\"", bundleName); //$NON-NLS-1$
return null;
}
return (MModelFragments) extensionRoot;
@@ -388,6 +642,33 @@
}
}
}
+
+ this.processorContributions.stream().filter(sr -> {
+ Dictionary<String, Object> dict = sr.getProperties();
+
+ Object before = dict.get(IModelProcessorContribution.BEFORE_FRAGMENT_PROPERTY_KEY);
+ boolean beforeFragments = true;
+ if (before instanceof Boolean) {
+ beforeFragments = (Boolean) before;
+ } else if (before instanceof String) {
+ beforeFragments = Boolean.parseBoolean((String) before);
+ }
+
+ Object applyObject = dict.get(IModelProcessorContribution.APPLY_PROPERTY_KEY);
+ String apply = applyObject instanceof String ? (String) applyObject
+ : IModelProcessorContribution.APPLY_ALWAYS;
+
+ // check if the value for apply is valid
+ if (!ALWAYS.equals(apply) && !INITIAL.equals(apply)) {
+ log(LogLevel.WARN,
+ "IModelProcessorContribution apply property value \"{0}\" is invalid, falling back to always", //$NON-NLS-1$
+ apply);
+ apply = IModelProcessorContribution.APPLY_ALWAYS;
+ }
+
+ return ((afterFragments != beforeFragments)
+ && (initial || IModelProcessorContribution.APPLY_ALWAYS.equals(apply)));
+ }).map(sr -> bundleContext.getService(sr)).forEach(ModelAssembler.this::runProcessor);
}
private void runProcessor(IConfigurationElement ce) {
@@ -398,7 +679,7 @@
String id = ceEl.getAttribute("id"); //$NON-NLS-1$
if (id == null) {
- logger.warn("No element id given"); //$NON-NLS-1$
+ log(LogLevel.WARN, "No element id given"); //$NON-NLS-1$
continue;
}
@@ -409,7 +690,7 @@
MApplicationElement el = ModelUtils.findElementById(application, id);
if (el == null) {
- logger.warn("Could not find element with id '" + id + "'"); //$NON-NLS-1$ //$NON-NLS-2$
+ log(LogLevel.WARN, "Could not find element with id '{0}'", id); //$NON-NLS-1$
}
localContext.set(key, el);
}
@@ -418,13 +699,56 @@
Object o = factory.create("bundleclass://" + ce.getContributor().getName() + "/" + ce.getAttribute("class"), //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
context, localContext);
if (o == null) {
- logger.warn("Unable to create processor " + ce.getAttribute("class") + " from " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- + ce.getContributor().getName());
+ log(LogLevel.WARN, "Unable to create processor {0} from {1}", //$NON-NLS-1$
+ ce.getAttribute("class"), //$NON-NLS-1$
+ ce.getContributor().getName());
} else {
- ContextInjectionFactory.invoke(o, Execute.class, context, localContext);
+ ContextInjectionFactory.invoke(o, Execute.class, context, localContext, null);
}
} catch (Exception e) {
- logger.warn(e, "Could not run processor"); //$NON-NLS-1$
+ log(LogLevel.WARN, "Could not run processor: {0}", e); //$NON-NLS-1$
+ }
+ }
+
+ private void runProcessor(IModelProcessorContribution processor) {
+ IEclipseContext localContext = EclipseContextFactory.create();
+
+ for (ModelElement element : processor.getModelElements()) {
+ String id = element.getId();
+
+ if (id == null) {
+ log(LogLevel.WARN, "No element id given"); //$NON-NLS-1$
+ continue;
+ }
+
+ String key = element.getContextKey();
+ if (key == null) {
+ key = id;
+ }
+
+ MApplicationElement el = ModelUtils.findElementById(application, id);
+ if (el == null) {
+ log(LogLevel.WARN, "Could not find element with id '{0}'", id); //$NON-NLS-1$
+ }
+ localContext.set(key, el);
+ }
+
+ try {
+ Object o = null;
+ if (processor.getProcessorClass() != null) {
+ o = ContextInjectionFactory.make(processor.getProcessorClass(), localContext);
+ } else {
+ o = processor;
+ }
+ if (o == null) {
+ log(LogLevel.WARN, "Unable to create processor {0} from {1}", //$NON-NLS-1$
+ processor.getProcessorClass().getName(),
+ FrameworkUtil.getBundle(processor.getProcessorClass()).getSymbolicName());
+ } else {
+ ContextInjectionFactory.invoke(o, Execute.class, context, localContext, null);
+ }
+ } catch (Exception e) {
+ log(LogLevel.WARN, "Could not run processor: {0}", e); //$NON-NLS-1$
}
}
@@ -466,8 +790,8 @@
el = importMaps.get((MApplicationElement) importObject);
if (el == null) {
- logger.warn("Could not resolve import for " //$NON-NLS-1$
- + ((MApplicationElement) importObject).getElementId());
+ log(LogLevel.WARN, "Could not resolve import for {0}", //$NON-NLS-1$
+ ((MApplicationElement) importObject).getElementId());
}
}
@@ -478,7 +802,7 @@
commands.add(() -> {
if (internalFeature.isMany()) {
- logger.error(MessageFormat.format(
+ log(LogLevel.ERROR, MessageFormat.format(
"Replacing in {0}.\n\nFeature={1}.\n\nInternalElement={2} contributed by {3}.\n\nImportObject={4}", //$NON-NLS-1$
interalTarget, internalFeature.getName(), internalElement.getElementId(),
internalElement.getContributorURI(), internalImportObject));
@@ -500,4 +824,59 @@
cmd.run();
}
}
+
+ void log(LogLevel level, String message, Object... args) {
+ Logger log = this.logger;
+ if (log != null) {
+ switch (level) {
+ case ERROR:
+ log.error(message, args);
+ break;
+ case WARN:
+ log.warn(message, args);
+ break;
+ case INFO:
+ log.info(message, args);
+ break;
+ case DEBUG:
+ log.debug(message, args);
+ break;
+ case AUDIT:
+ log.audit(message, args);
+ break;
+ case TRACE:
+ log.trace(message, args);
+ break;
+ }
+ } else {
+ // fallback if no LogService is available
+ if (LogLevel.ERROR == level) {
+ System.err.println(MessageFormat.format(message, args));
+ } else {
+ System.out.println(MessageFormat.format(message, args));
+ }
+ }
+ }
+
+ /**
+ *
+ * @param factory The {@link LoggerFactory} to retrieve the {@link Logger} from.
+ */
+ @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
+ public void setLogger(LoggerFactory factory) {
+ this.factory = factory;
+ this.logger = factory.getLogger(getClass());
+ }
+
+ /**
+ *
+ * @param loggerFactory The {@link LoggerFactory} that was used to retrieve the
+ * {@link Logger}.
+ */
+ public void unsetLogger(LoggerFactory loggerFactory) {
+ if (this.factory == loggerFactory) {
+ this.factory = null;
+ this.logger = null;
+ }
+ }
}
diff --git a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ResourceHandler.java b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ResourceHandler.java
index 947a8c7..ce5d396 100644
--- a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ResourceHandler.java
+++ b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/ResourceHandler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2009, 2016 IBM Corporation and others.
+ * Copyright (c) 2009, 2021 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -192,9 +192,12 @@
MApplication appElement = (MApplication) resource.getContents().get(0);
this.context.set(MApplication.class, appElement);
- ModelAssembler contribProcessor = ContextInjectionFactory.make(ModelAssembler.class,
- context);
- contribProcessor.processModel(initialModel);
+
+ ModelAssembler mac = context.get(ModelAssembler.class);
+ if (mac != null) {
+ ContextInjectionFactory.invoke(mac, PostConstruct.class, context);
+ mac.processModel(initialModel);
+ }
if (!hasTopLevelWindows(resource) && logger != null) {
logger.error(new Exception(), // log a stack trace to help debug the
diff --git a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/workbench/modeling/IModelProcessorContribution.java b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/workbench/modeling/IModelProcessorContribution.java
new file mode 100644
index 0000000..f5d3b8f
--- /dev/null
+++ b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/workbench/modeling/IModelProcessorContribution.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Dirk Fauth and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
+ ******************************************************************************/
+
+package org.eclipse.e4.ui.workbench.modeling;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Service component interface to be able to register model processors via OSGi
+ * services.
+ * <p>
+ * Programmatic processor that gets called with the intention of modifying UI
+ * model.
+ * </p>
+ *
+ * @since 1.13
+ */
+public interface IModelProcessorContribution {
+
+ /**
+ * Service property key for specifying the beforeFragment attribute, which
+ * specifies if the processor has to be invoked before model fragments are
+ * added. If not specified it defaults to <code>true</code>.
+ */
+ String BEFORE_FRAGMENT_PROPERTY_KEY = "beforefragment"; //$NON-NLS-1$
+ /**
+ * Service property key for specifying the beforeFragment attribute, which
+ * specifies if the processor has to be invoked before model fragments are
+ * added. If not specified it defaults to <code>true</code>.
+ * <p>
+ * This constant can be used to simplify the property definition in the
+ * Component annotation:<br>
+ * <code>@Component(property = { IModelProcessorContribution.BEFORE_FRAGMENT_PROPERTY_PREFIX + "false" })</code>
+ * </p>
+ */
+ String BEFORE_FRAGMENT_PROPERTY_PREFIX = "beforefragment:Boolean="; //$NON-NLS-1$
+ /**
+ * Service property key for specifying the apply attribute, which defines in
+ * which case a processor is run. If not specified it defaults to <i>always</i>.
+ */
+ String APPLY_PROPERTY_KEY = "apply"; //$NON-NLS-1$
+ /**
+ * Service property key for specifying the apply attribute, which defines in
+ * which case a processor is run. If not specified it defaults to <i>always</i>.
+ * <p>
+ * This constant can be used to simplify the property definition in the
+ * Component annotation:<br>
+ * <code>@Component(property = { IModelProcessorContribution.APPLY_PROPERTY_PREFIX + "initial" })</code>
+ * </p>
+ */
+ String APPLY_PROPERTY_PREFIX = "apply="; //$NON-NLS-1$
+ /**
+ * Value for the <code>apply</code> attribute. If set the processor is executed
+ * each time the application is started.
+ */
+ String APPLY_ALWAYS = "always"; //$NON-NLS-1$
+ /**
+ * Value for the <code>apply</code> attribute. If set the processor is executed
+ * only when coming from a none persistent state.
+ */
+ String APPLY_INITIAL = "initial"; //$NON-NLS-1$
+
+ /**
+ *
+ * @return Java class containing model processor. A class method with the
+ * qualifier <code>"org.eclipse.e4.core.di.annotations.Execute"</code>,
+ * will be invoked as a part of the model processing. If this method
+ * returns <code>null</code> it is expected that this
+ * {@link IModelProcessorContribution} contains a method with the
+ * qualifier <code>"org.eclipse.e4.core.di.annotations.Execute"</code>.
+ */
+ default Class<?> getProcessorClass() {
+ return null;
+ }
+
+ /**
+ *
+ * @return model elements to be added to the context used to invoke the
+ * processor.
+ */
+ default List<ModelElement> getModelElements() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * A model element to be added to the context used to invoke the processor.
+ */
+ class ModelElement {
+ private final String id;
+ private final String contextKey;
+
+ /**
+ *
+ * @param id Identifier of a model element to be added to the context.
+ */
+ public ModelElement(String id) {
+ this(id, null);
+ }
+
+ /**
+ *
+ * @param id Identifier of a model element to be added to the context.
+ * @param contextKey An optional key under which to store the model element in
+ * the context. The value of "id" is used if this value is not
+ * specified.
+ */
+ public ModelElement(String id, String contextKey) {
+ this.id = id;
+ this.contextKey = contextKey;
+ }
+
+ /**
+ * @return Identifier of a model element to be added to the context.
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @return An optional key under which to store the model element in the
+ * context. The value of "id" is used if this value is not specified.
+ */
+ public String getContextKey() {
+ return contextKey;
+ }
+
+ }
+}
diff --git a/tests/org.eclipse.e4.ui.tests/pom.xml b/tests/org.eclipse.e4.ui.tests/pom.xml
index 78aebe4..49a0a26 100644
--- a/tests/org.eclipse.e4.ui.tests/pom.xml
+++ b/tests/org.eclipse.e4.ui.tests/pom.xml
@@ -39,5 +39,5 @@
</plugin>
</plugins>
</build>
-
+
</project>
diff --git a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/application/ResourceHandlerTest.java b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/application/ResourceHandlerTest.java
index 214aa4f..3c6488f 100644
--- a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/application/ResourceHandlerTest.java
+++ b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/application/ResourceHandlerTest.java
@@ -25,6 +25,7 @@
import java.util.List;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.e4.ui.internal.workbench.E4Workbench;
import org.eclipse.e4.ui.internal.workbench.E4XMIResource;
import org.eclipse.e4.ui.internal.workbench.ResourceHandler;
@@ -38,10 +39,12 @@
import org.eclipse.e4.ui.model.application.ui.menu.MMenu;
import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
import org.eclipse.e4.ui.workbench.IWorkbench;
+import org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.swt.widgets.Display;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.BundleContext;
@@ -82,6 +85,8 @@
localContext.set(E4Workbench.INITIAL_WORKBENCH_MODEL_URI, uri);
+ localContext.set(UISynchronize.class, new DisplayUISynchronize(Display.getDefault()));
+
return ContextInjectionFactory.make(ResourceHandler.class, localContext);
}
diff --git a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerFragmentOrderingTests.java b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerFragmentOrderingTests.java
index 1b3ea76..ce4c846 100644
--- a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerFragmentOrderingTests.java
+++ b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerFragmentOrderingTests.java
@@ -17,7 +17,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.HashSet;
@@ -25,9 +24,10 @@
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
+import javax.annotation.PostConstruct;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
-import org.eclipse.e4.core.services.log.Logger;
+import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.e4.ui.internal.workbench.E4XMIResource;
import org.eclipse.e4.ui.internal.workbench.E4XMIResourceFactory;
import org.eclipse.e4.ui.internal.workbench.ModelAssembler;
@@ -49,10 +49,12 @@
import org.eclipse.e4.ui.model.fragment.MFragmentFactory;
import org.eclipse.e4.ui.model.fragment.MModelFragments;
import org.eclipse.e4.ui.model.fragment.MStringModelFragment;
+import org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.swt.widgets.Display;
import org.junit.Before;
import org.junit.Test;
@@ -69,7 +71,6 @@
private ResourceSetImpl resourceSet;
private E4XMIResource appResource;
private ModelAssembler assembler;
- private Logger logger;
private MToolBar toolBar;
private static final String MAIN_TOOLBAR_ID = "org.eclipse.e4.ui.tests.modelassembler.fragmentordering.mainWindow.mainToolBar";
private static final String MAIN_WINDOW_ID = "org.eclipse.e4.ui.tests.modelassembler.fragmentordering.mainWindow";
@@ -81,10 +82,8 @@
application.setElementId(APPLICATION_ID);
application.setContext(appContext);
- logger = mock(Logger.class);
-
- appContext.set(Logger.class, logger);
appContext.set(MApplication.class, application);
+ appContext.set(UISynchronize.class, new DisplayUISynchronize(Display.getDefault()));
factory = new E4XMIResourceFactory();
appResource = (E4XMIResource) factory.createResource(URI.createURI("virtualuri"));
@@ -92,7 +91,7 @@
resourceSet.getResources().add(appResource);
appResource.getContents().add((EObject) application);
assembler = new ModelAssembler();
- ContextInjectionFactory.inject(assembler, appContext);
+ ContextInjectionFactory.invoke(assembler, PostConstruct.class, appContext);
createWindowWithToolbar();
}
diff --git a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerTests.java b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerTests.java
index 6e5376d..af4fb5d 100644
--- a/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerTests.java
+++ b/tests/org.eclipse.e4.ui.tests/src/org/eclipse/e4/ui/tests/workbench/ModelAssemblerTests.java
@@ -21,11 +21,13 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
+import javax.annotation.PostConstruct;
import org.eclipse.core.internal.registry.ExtensionRegistry;
import org.eclipse.core.runtime.ContributorFactorySimple;
import org.eclipse.core.runtime.IContributor;
@@ -35,7 +37,7 @@
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
-import org.eclipse.e4.core.services.log.Logger;
+import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.e4.ui.internal.workbench.E4XMIResource;
import org.eclipse.e4.ui.internal.workbench.E4XMIResourceFactory;
import org.eclipse.e4.ui.internal.workbench.ExtensionsSort;
@@ -55,14 +57,19 @@
import org.eclipse.e4.ui.model.fragment.MModelFragments;
import org.eclipse.e4.ui.model.fragment.MStringModelFragment;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
+import org.eclipse.e4.ui.workbench.swt.DisplayUISynchronize;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.swt.widgets.Display;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.osgi.service.log.Logger;
+import org.osgi.service.log.LoggerFactory;
@SuppressWarnings("nls")
public class ModelAssemblerTests {
@@ -86,17 +93,25 @@
application.setContext(appContext);
logger = mock(Logger.class);
+ LoggerFactory logFactory = mock(LoggerFactory.class);
+ when(logFactory.getLogger((Class<?>) ArgumentMatchers.any())).thenReturn(logger);
appContext.set(Logger.class, logger);
appContext.set(MApplication.class, application);
+ appContext.set(UISynchronize.class, new DisplayUISynchronize(Display.getDefault()));
factory = new E4XMIResourceFactory();
appResource = (E4XMIResource) factory.createResource(URI.createURI("virtualuri"));
resourceSet = new ResourceSetImpl();
resourceSet.getResources().add(appResource);
appResource.getContents().add((EObject) application);
+
+ // FIXME set references programmatically
assembler = new ModelAssembler();
- ContextInjectionFactory.inject(assembler, appContext);
+ ContextInjectionFactory.invoke(assembler, PostConstruct.class, appContext);
+
+ assembler.setLogger(logFactory);
+
modelService = application.getContext().get(EModelService.class);
}
@@ -295,7 +310,7 @@
assembler.resolveImports(imports, addedElements);
assertEquals(null, placeholder.getRef());
- verify(logger).warn("Could not resolve import for null");
+ verify(logger).warn("Could not resolve import for {0}", new Object[] { null });
verifyZeroInteractions(logger);
}
@@ -325,6 +340,8 @@
/* contribute fragment with imports and post-processor */
IContributor contributor = ContributorFactorySimple.createContributor(BUNDLE_SYMBOLIC_NAME);
IExtensionRegistry registry = createTestExtensionRegistry();
+ assembler.setExtensionRegistry(registry);
+
assertEquals(0, registry.getConfigurationElementsFor(EXTENSION_POINT_ID).length);
// The fragment contributes a Placeholder to the application's Area. The
// Placeholder references the Part that we created above.
@@ -463,7 +480,8 @@
@Test
public void testProcessor_noProcessor() throws Exception {
testProcessor("org.eclipse.e4.ui.tests/data/ModelAssembler/processor_null.xml", true, false);
- verify(logger).warn("Unable to create processor null from org.eclipse.e4.ui.tests");
+ verify(logger).warn("Unable to create processor {0} from {1}",
+ new Object[] { null, "org.eclipse.e4.ui.tests" });
assertEquals(0, application.getDescriptors().size());
verifyZeroInteractions(logger);
}
@@ -477,8 +495,9 @@
@Test
public void testProcessor_processorNotFound() throws Exception {
testProcessor("org.eclipse.e4.ui.tests/data/ModelAssembler/processor_wrongProcessorClass.xml", true, false);
- verify(logger).warn(
- "Unable to create processor org.eclipse.e4.ui.tests.workbench.SimplePreProcessor_NotFound from org.eclipse.e4.ui.tests");
+ verify(logger).warn("Unable to create processor {0} from {1}",
+ new Object[] { "org.eclipse.e4.ui.tests.workbench.SimplePreProcessor_NotFound",
+ "org.eclipse.e4.ui.tests" });
assertEquals(0, application.getDescriptors().size());
verifyZeroInteractions(logger);
}
@@ -495,7 +514,8 @@
public void testProcessor_wrongAppId() throws Exception {
application.setElementId("newID");
testProcessor("org.eclipse.e4.ui.tests/data/ModelAssembler/processors_initial.xml", true, true);
- verify(logger).warn("Could not find element with id 'org.eclipse.e4.ui.tests.modelassembler.app'");
+ verify(logger).warn("Could not find element with id '{0}'",
+ new Object[] { "org.eclipse.e4.ui.tests.modelassembler.app" });
verifyZeroInteractions(logger);
assertEquals(1, application.getDescriptors().size());
assertEquals("simpleprocessor.post", application.getDescriptors().get(0).getElementId());