blob: d3885039943c4ebe64f3ffef3a693aa4e5d43d6d [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2014-2016 itemis 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:
* itemis - Initial API and implementation
* itemis - [463980] org.eclipse.sphinx.emf.mwe.dynamic.operations.BasicWorkflowRunnerOperation.run(IProgressMonitor) should not be final
* itemis - [503063] Provide launching support for Sphinx Workflows
*
* </copyright>
*/
package org.eclipse.sphinx.emf.mwe.dynamic.operations;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
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.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.mwe2.runtime.workflow.IWorkflow;
import org.eclipse.emf.mwe2.runtime.workflow.IWorkflowComponent;
import org.eclipse.emf.mwe2.runtime.workflow.IWorkflowContext;
import org.eclipse.emf.mwe2.runtime.workflow.Workflow;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.osgi.util.NLS;
import org.eclipse.sphinx.emf.model.IModelDescriptor;
import org.eclipse.sphinx.emf.model.ModelDescriptorRegistry;
import org.eclipse.sphinx.emf.mwe.dynamic.IWorkflowHandler;
import org.eclipse.sphinx.emf.mwe.dynamic.ModelWorkflowContext;
import org.eclipse.sphinx.emf.mwe.dynamic.WorkflowContributorRegistry;
import org.eclipse.sphinx.emf.mwe.dynamic.WorkflowHandlerRegistry;
import org.eclipse.sphinx.emf.mwe.dynamic.WorkspaceWorkflow;
import org.eclipse.sphinx.emf.mwe.dynamic.components.IModelWorkflowComponent;
import org.eclipse.sphinx.emf.mwe.dynamic.internal.Activator;
import org.eclipse.sphinx.emf.mwe.dynamic.internal.messages.Messages;
import org.eclipse.sphinx.emf.mwe.dynamic.util.XtendUtil;
import org.eclipse.sphinx.emf.saving.SaveIndicatorUtil;
import org.eclipse.sphinx.emf.util.EcorePlatformUtil;
import org.eclipse.sphinx.emf.util.EcoreResourceUtil;
import org.eclipse.sphinx.emf.util.WorkspaceTransactionUtil;
import org.eclipse.sphinx.emf.workspace.loading.ModelLoadManager;
import org.eclipse.sphinx.jdt.loaders.ProjectClassLoader;
import org.eclipse.sphinx.platform.operations.AbstractLabeledRunnable;
import org.eclipse.sphinx.platform.operations.AbstractWorkspaceOperation;
import org.eclipse.sphinx.platform.operations.ILabeledRunnable;
import org.eclipse.sphinx.platform.util.StatusUtil;
public class BasicWorkflowRunnerOperation extends AbstractWorkspaceOperation implements IWorkflowRunnerOperation {
private Object workflow;
private Workflow workflowInstance = null;
private Object model;
private List<URI> modelURIs = new ArrayList<URI>();
private Set<IModelDescriptor> modelDescriptors = new HashSet<IModelDescriptor>();
private Set<Resource> emfModelResources = new HashSet<Resource>();
private boolean autoSave = false;
private Map<String, Object> arguments = new HashMap<String, Object>();
public BasicWorkflowRunnerOperation(String label, Object workflow) {
super(label);
this.workflow = workflow;
}
/*
* @see org.eclipse.sphinx.emf.mwe.dynamic.operations.IWorkflowRunnerOperation#getWorkflow()
*/
@Override
public Object getWorkflow() {
return workflow;
}
/*
* @see org.eclipse.sphinx.emf.mwe.dynamic.operations.IWorkflowRunnerOperation#getModel()
*/
@Override
public Object getModel() {
return model;
}
/*
* @see org.eclipse.sphinx.emf.mwe.dynamic.operations.IWorkflowRunnerOperation#getModelURIs()
*/
@Override
public List<URI> getModelURIs() {
return modelURIs;
}
/*
* @see org.eclipse.sphinx.emf.mwe.dynamic.operations.IWorkflowRunnerOperation#isAutoSave()
*/
@Override
public boolean isAutoSave() {
return autoSave;
}
/*
* @see org.eclipse.sphinx.emf.mwe.dynamic.operations.IWorkflowRunnerOperation#setAutoSave(boolean)
*/
@Override
public void setAutoSave(boolean autoSave) {
this.autoSave = autoSave;
}
/*
* @see org.eclipse.sphinx.emf.mwe.dynamic.operations.IWorkflowRunnerOperation#getArguments()
*/
@Override
public Map<String, Object> getArguments() {
return arguments;
}
/*
* @see org.eclipse.sphinx.platform.operations.IWorkspaceOperation#getRule()
*/
@Override
public ISchedulingRule getRule() {
// TODO Compute scheduling rule by combining scheduling rules from workflow components
return null;
}
/*
* @see org.eclipse.core.resources.IWorkspaceRunnable#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void run(final IProgressMonitor monitor) throws CoreException, OperationCanceledException {
try {
SubMonitor progress = SubMonitor.convert(monitor, 100);
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
final Workflow workflowInstance = getWorkflowInstance();
if (workflowInstance == null) {
return;
}
// Load selected Sphinx/EMF model file (if any)
// FIXME Don't pass model around - just let methods directly access the field
model = loadModel(progress.newChild(5));
final IWorkflowContext context = createWorkflowContext(model, arguments, progress.newChild(90));
// Pre-run handers sorted by their priority
List<IWorkflowHandler> sortedHandlers = WorkflowHandlerRegistry.INSTANCE
.getSortedHandlers(workflowInstance.getClass().asSubclass(IWorkflow.class));
for (IWorkflowHandler workflowHandler : sortedHandlers) {
workflowHandler.preRun(workflowInstance, context);
}
ILabeledRunnable runnable = new AbstractLabeledRunnable(getLabel()) {
@Override
public void run() {
workflowInstance.run(context);
}
};
// Workflow dealing with some model?
// FIXME Don't pass model around - just let methods directly access the field
TransactionalEditingDomain editingDomain = getEditingDomain(model);
if (editingDomain != null && hasModelWorkflowComponents(workflowInstance)) {
// Workflow intending to modify the model?
if (isModifyingModel(workflowInstance)) {
// Execute in write transaction
WorkspaceTransactionUtil.executeInWriteTransaction(editingDomain, runnable);
} else {
// Execute in read transaction
editingDomain.runExclusive(runnable);
}
} else {
// Execute right away
runnable.run();
}
// Post-run handlers
for (IWorkflowHandler workflowHandler : sortedHandlers) {
workflowHandler.postRun(workflowInstance, context);
}
// Save model if needed
if (isAutoSave()) {
saveModel(progress.newChild(5));
} else {
progress.worked(5);
}
} catch (OperationCanceledException ex) {
throw ex;
} catch (Exception ex) {
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
throw new CoreException(status);
}
}
protected IWorkflowContext createWorkflowContext(Object model, Map<String, Object> arguments, IProgressMonitor monitor) {
return new ModelWorkflowContext(model, arguments, monitor);
}
protected Workflow getWorkflowInstance() throws CoreException {
try {
if (workflowInstance == null) {
Class<Workflow> workflowClass = null;
IType workflowType = getWorkflowType();
if (workflowType != null) {
workflowClass = loadWorkflowClass(workflowType);
} else {
workflowClass = getWorkflowClass();
}
if (workflowClass != null) {
workflowInstance = workflowClass.newInstance();
}
}
return workflowInstance;
} catch (CoreException ex) {
throw ex;
} catch (Exception ex) {
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
throw new CoreException(status);
}
}
protected IType getWorkflowType() {
if (workflow instanceof IType) {
return (IType) workflow;
}
if (workflow instanceof ICompilationUnit) {
return ((ICompilationUnit) workflow).findPrimaryType();
}
if (workflow instanceof IFile) {
IJavaElement workflowJavaElement = XtendUtil.getJavaElement((IFile) workflow);
if (workflowJavaElement instanceof ICompilationUnit) {
return ((ICompilationUnit) workflowJavaElement).findPrimaryType();
}
}
return null;
}
protected Class<Workflow> getWorkflowClass() throws CoreException {
if (workflow instanceof Class<?>) {
Class<?> clazz = (Class<?>) workflow;
if (!Workflow.class.isAssignableFrom(clazz)) {
Exception ex = new IllegalStateException("Workflow class '" + clazz.getName() + "' is not a subclass of " + Workflow.class.getName()); //$NON-NLS-1$ //$NON-NLS-2$
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
throw new CoreException(status);
}
@SuppressWarnings("unchecked")
Class<Workflow> workflowClass = (Class<Workflow>) clazz;
return workflowClass;
}
return null;
}
protected Class<Workflow> loadWorkflowClass(IType workflowType) throws CoreException {
Assert.isNotNull(workflowType);
try {
// Find out where given workflow type originates from
if (!workflowType.isBinary()) {
// Workflow type refers to an on-the-fly compiled Java class in the runtime workspace
// Create project class loader capable of loading Java class behind workflow type from underlying Java
// or plug-in project in runtime workspace
ProjectClassLoader projectClassLoader = new ProjectClassLoader(workflowType.getJavaProject());
// TODO Surround with appropriate tracing option
// ClassLoaderExtensions.printHierarchy(projectClassLoader);
// Use project class loader to load Java class behind workflow type
Class<?> clazz = projectClassLoader.loadClass(workflowType.getFullyQualifiedName());
if (!Workflow.class.isAssignableFrom(clazz)) {
throw new IllegalStateException("Workflow class '" + clazz.getName() + "' is not a subclass of " + Workflow.class.getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
@SuppressWarnings("unchecked")
Class<Workflow> workflowClass = (Class<Workflow>) clazz;
return workflowClass;
} else {
// Workflow type refers to a binary Java class from the running Eclipse instance
// Load Java class behind workflow type from underlying contributor plug-in
return WorkflowContributorRegistry.INSTANCE.loadContributedWorkflowClass(workflowType);
}
} catch (CoreException ex) {
throw ex;
} catch (Exception ex) {
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
throw new CoreException(status);
}
}
protected TransactionalEditingDomain getEditingDomain(Object model) {
if (model instanceof EObject || model instanceof Resource) {
return TransactionUtil.getEditingDomain(model);
}
if (model instanceof List) {
List<?> models = (List<?>) model;
if (!models.isEmpty()) {
return getEditingDomain(models.get(0));
}
}
return null;
}
protected boolean hasModelWorkflowComponents(Workflow workflow) {
if (workflow instanceof WorkspaceWorkflow) {
List<IWorkflowComponent> children = ((WorkspaceWorkflow) workflow).getChildren();
for (IWorkflowComponent component : children) {
if (component instanceof IModelWorkflowComponent) {
return true;
}
}
}
return false;
}
protected boolean isModifyingModel(Workflow workflow) {
if (workflow instanceof WorkspaceWorkflow) {
List<IWorkflowComponent> children = ((WorkspaceWorkflow) workflow).getChildren();
for (IWorkflowComponent component : children) {
if (component instanceof IModelWorkflowComponent) {
if (((IModelWorkflowComponent) component).isModifyingModel()) {
return true;
}
}
}
}
return false;
}
protected Object loadModel(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
if (modelURIs.isEmpty()) {
return null;
}
final SubMonitor progress = SubMonitor.convert(monitor, modelURIs.size() * 2);
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
List<EObject> modelObjects = new ArrayList<EObject>();
// Loads Sphinx integrated models
// FIXME Pass appropriate SubMonitor instance rather than monitor directly
ModelLoadManager.INSTANCE.loadURIs(modelURIs, false, progress.newChild(modelURIs.size()));
// FIXME ResourceSet for regular EMF model files must not go out of scope and resources must be unloaded after
// workflow execution; implement this ResourceSet as field and use it as replacement for emfModelResources
ResourceSet resouceSet = new ResourceSetImpl();
for (URI modelURI : modelURIs) {
IFile file = EcorePlatformUtil.getFile(modelURI.trimFragment());
// Sphinx integrated model
if (ModelDescriptorRegistry.INSTANCE.isModelFile(file)) {
String fragment = modelURI.fragment();
if (fragment != null) {
// URI refers to a model object
EObject eObject = EcorePlatformUtil.getEObject(modelURI);
if (eObject == null) {
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(),
NLS.bind(Messages.error_modelResourceContainsNoMatchingElement, modelURI.toPlatformString(true), fragment));
throw new CoreException(status);
}
modelObjects.add(eObject);
IModelDescriptor modelDescriptor = ModelDescriptorRegistry.INSTANCE.getModel(eObject.eResource());
modelDescriptors.add(modelDescriptor);
} else {
// URI refers to a model resource
// Exclude Xtend files from being considered as workflow models
if (!XtendUtil.isXtendResource(modelURI)) {
Resource modelResource = EcorePlatformUtil.getResource(modelURI);
if (modelResource == null) {
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(),
NLS.bind(Messages.error_modelResourceCouldNotBeLoaded, modelURI.toPlatformString(true)));
throw new CoreException(status);
}
modelObjects.addAll(modelResource.getContents());
IModelDescriptor modelDescriptor = ModelDescriptorRegistry.INSTANCE.getModel(modelResource);
modelDescriptors.add(modelDescriptor);
}
}
} else {
// Regular EMF model
String fragment = modelURI.fragment();
if (fragment != null) {
// URI refers to a model object
EObject eObject = EcoreResourceUtil.loadEObject(resouceSet, modelURI);
if (eObject == null) {
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(),
NLS.bind(Messages.error_modelResourceContainsNoMatchingElement, modelURI.toPlatformString(true), fragment));
throw new CoreException(status);
}
modelObjects.add(eObject);
emfModelResources.add(eObject.eResource());
} else {
// URI refers to a model resource
// Exclude Xtend files from being considered as workflow models
if (!XtendUtil.isXtendResource(modelURI)) {
Resource modelResource = EcoreResourceUtil.loadResource(resouceSet, modelURI, null);
if (modelResource == null) {
IStatus status = StatusUtil.createErrorStatus(Activator.getPlugin(),
NLS.bind(Messages.error_modelResourceCouldNotBeLoaded, modelURI.toPlatformString(true)));
throw new CoreException(status);
}
modelObjects.addAll(modelResource.getContents());
emfModelResources.add(modelResource);
}
}
}
progress.worked(1);
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
}
return !modelObjects.isEmpty() ? modelObjects : null;
}
protected void saveModel(IProgressMonitor monitor) {
final SubMonitor progress = SubMonitor.convert(monitor, modelDescriptors.size() + emfModelResources.size());
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
// Save Sphinx integrated models
for (IModelDescriptor modelDescriptor : modelDescriptors) {
if (SaveIndicatorUtil.isDirty(modelDescriptor)) {
EcorePlatformUtil.saveModel(modelDescriptor, false, progress.newChild(1));
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
} else {
progress.worked(1);
}
}
// Save regular EMF models
for (Resource modelResource : emfModelResources) {
EcoreResourceUtil.saveModelResource(modelResource, null);
progress.worked(1);
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
}
}
}