/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.gui.workflow.execute;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.ui.PlatformUI;
import de.rcenvironment.core.communication.api.NodeIdentifierService;
import de.rcenvironment.core.communication.api.PlatformService;
import de.rcenvironment.core.communication.common.LogicalNodeId;
import de.rcenvironment.core.communication.common.NodeIdentifierContextHolder;
import de.rcenvironment.core.component.api.ComponentConstants;
import de.rcenvironment.core.component.api.DistributedComponentKnowledge;
import de.rcenvironment.core.component.api.DistributedComponentKnowledgeService;
import de.rcenvironment.core.component.model.api.ComponentInstallation;
import de.rcenvironment.core.component.spi.DistributedComponentKnowledgeListener;
import de.rcenvironment.core.component.validation.api.ComponentValidationMessageStore;
import de.rcenvironment.core.component.workflow.execution.api.ConsoleRowModelService;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowDescriptionValidationResult;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionContext;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionContextBuilder;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionException;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionInformation;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionService;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionUtils;
import de.rcenvironment.core.component.workflow.model.api.Connection;
import de.rcenvironment.core.component.workflow.model.api.WorkflowDescription;
import de.rcenvironment.core.component.workflow.model.api.WorkflowDescriptionPersistenceHandler;
import de.rcenvironment.core.component.workflow.model.api.WorkflowNode;
import de.rcenvironment.core.configuration.ConfigurationService;
import de.rcenvironment.core.gui.workflow.Activator;
import de.rcenvironment.core.gui.workflow.editor.validator.WorkflowDescriptionValidationUtils;
import de.rcenvironment.core.gui.workflow.view.WorkflowRunEditorAction;
import de.rcenvironment.core.gui.workflow.view.properties.InputModel;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.rpc.RemoteOperationException;
import de.rcenvironment.core.utils.incubator.ServiceRegistry;
import de.rcenvironment.core.utils.incubator.ServiceRegistryPublisherAccess;
/**
* {@link Wizard} to start the execution of a workflow.
*
* @author Christian Weiss
* @author Doreen Seider
* @author Goekhan Guerkan
* @author Jascha Riedel
* @author Robert Mischke
*/
public class WorkflowExecutionWizard extends Wizard implements DistributedComponentKnowledgeListener {
private static final int MINIMUM_HEIGHT = 250;
private static final int MINIMUM_WIDTH = 500;
private static final Log LOG = LogFactory.getLog(WorkflowExecutionWizard.class);
private final boolean inputTabEnabled;
private final IFile wfFile;
private WorkflowDescription wfDescription;
private List<WorkflowNode> disabledWorkflowNodes;
private List<Connection> disabledConnections;
private final ServiceRegistryPublisherAccess serviceRegistryAccess;
private final WorkflowExecutionService workflowExecutionService;
private final NodeIdentifierConfigurationHelper nodeIdConfigHelper;
private WorkflowPage workflowPage;
private PlaceholderPage placeholdersPage;
private LogicalNodeId localDefaultNodeId;
private boolean errorVisible = false;
private ComponentValidationMessageStore messageStore = ComponentValidationMessageStore.getInstance();
public WorkflowExecutionWizard(final IFile workflowFile, WorkflowDescription workflowDescription) {
serviceRegistryAccess = ServiceRegistry.createPublisherAccessFor(this);
workflowExecutionService = serviceRegistryAccess.getService(WorkflowExecutionService.class);
Activator.getInstance().registerUndisposedWorkflowShutdownListener();
this.inputTabEnabled = serviceRegistryAccess.getService(ConfigurationService.class)
.getConfigurationSegment("general").getBoolean(ComponentConstants.CONFIG_KEY_ENABLE_INPUT_TAB, false);
this.wfFile = workflowFile;
this.disabledWorkflowNodes = WorkflowExecutionUtils.getDisabledWorkflowNodes(workflowDescription);
this.disabledConnections = workflowDescription.removeWorkflowNodesAndRelatedConnections(disabledWorkflowNodes);
this.wfDescription = workflowDescription;
nodeIdConfigHelper = new NodeIdentifierConfigurationHelper();
// cache the local instance for later use
this.localDefaultNodeId = serviceRegistryAccess.getService(PlatformService.class).getLocalDefaultLogicalNodeId();
wfDescription.setName(
WorkflowExecutionUtils.generateDefaultNameforExecutingWorkflow(workflowFile.getName(), wfDescription));
wfDescription.setFileName(workflowFile.getName());
// set the title of the wizard dialog
setWindowTitle(Messages.workflowExecutionWizardTitle);
// display a progress monitor
setNeedsProgressMonitor(true);
ColorPalette.getInstance().loadColors();
serviceRegistryAccess.registerService(DistributedComponentKnowledgeListener.class, this);
// this is currently required for nested calls to WorkflowDescription.clone(), which indirectly use id deserialization - misc_ro
NodeIdentifierContextHolder
.setDeserializationServiceForCurrentThread(serviceRegistryAccess.getService(NodeIdentifierService.class));
}
@Override
public void addPages() {
workflowPage = new WorkflowPage(wfDescription.clone(), nodeIdConfigHelper);
addPage(workflowPage);
placeholdersPage = new PlaceholderPage(wfDescription.clone());
addPage(placeholdersPage);
getShell().setMinimumSize(MINIMUM_WIDTH, MINIMUM_HEIGHT);
}
@Override
public boolean canFinish() {
return workflowPage.canFinish()
&& (getContainer().getCurrentPage() == placeholdersPage || placeholdersPage.canFinish());
}
@Override
public boolean performFinish() {
grabDataFromWorkflowPage();
grabDataFromPlaceholdersPage();
if (!performValidations()) {
return false;
}
if (!validateWorkflowAndPlaceholders() && !requestConfirmationForValidationErrorsWarnings()) {
return false;
} else {
//Only save placeholders to persistent settings if the user does not cancel the run dialog.
placeholdersPage.savePlaceholdersToPersistentSettings();
}
placeholdersPage.dispose();
WorkflowExecutionUtils.setNodeIdentifiersToTransientInCaseOfLocalOnes(wfDescription, localDefaultNodeId);
saveWorkflow();
// clone the wf description here to make sure that the description instance passed to the workflow engine is not modified by the
// GUI, can and should be changed with #0012071 e.g., by introducing and using an immutable representation of the workflow
executeWorkflowInBackground(wfDescription.clone());
return true;
}
private void executeWorkflowInBackground(final WorkflowDescription clonedWfDescription) {
Job job = new Job(Messages.workflowExecutionWizardTitle) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
monitor.beginTask(Messages.settingUpWorkflow, 2);
monitor.worked(1);
executeWorkflow(clonedWfDescription);
monitor.worked(1);
return Status.OK_STATUS;
} finally {
monitor.done();
}
};
};
job.setUser(true);
job.schedule();
}
/**
* @return true if all selected instances available.
*/
public synchronized boolean performValidations() {
WorkflowDescriptionValidationResult validationResult = workflowExecutionService
.validateWorkflowDescription(wfDescription);
workflowPage.getWorkflowComposite().refreshContent();
if (getContainer().getCurrentPage() == placeholdersPage) {
if (!validationResult.isSucceeded()) {
// MessageDialog.openError(getShell(), "Instances Error", "Some
// instances selected, are not available anymore:\n\n"
// + validationResult.toString() + "\n\nCheck your connection(s)
// or select (an)other instance(s).");
if (!errorVisible) {
errorVisible = true;
MessageBox errorBox = new MessageBox(Display.getCurrent().getActiveShell(), SWT.ICON_ERROR | SWT.OK);
errorBox.setMessage("Some of the selected instances are not available anymore:\n\n"
+ validationResult.toString() + "\n\nCheck your connection(s) or select (an)other instance(s).");
errorBox.setText("Instances Error");
int id = errorBox.open();
if (id == SWT.OK || id == SWT.CLOSE) {
errorVisible = false;
}
}
return false;
}
}
if (validationResult.isSucceeded()) {
return true;
}
return false;
}
private void grabDataFromWorkflowPage() {
wfDescription.setName(workflowPage.getWorkflowName());
wfDescription.setControllerNode(workflowPage.getControllerNodeId());
wfDescription.setAdditionalInformation(workflowPage.getAdditionalInformation());
Map<String, ComponentInstallation> cmpInstallations = workflowPage.getComponentInstallations();
for (String wfNodeId : cmpInstallations.keySet()) {
wfDescription.getWorkflowNode(wfNodeId).getComponentDescription()
.setComponentInstallationAndUpdateConfiguration(cmpInstallations.get(wfNodeId));
}
}
private void grabDataFromPlaceholdersPage() {
placeholdersPage.performFinish();
Map<String, Map<String, String>> placeholders = placeholdersPage.getPlaceholders();
for (String wfNodeId : placeholders.keySet()) {
wfDescription.getWorkflowNode(wfNodeId).getComponentDescription().getConfigurationDescription()
.setPlaceholders(placeholders.get(wfNodeId));
}
}
/**
* Validate the executed workflow and the placeholder page, if any warnings or errors exist.
*
* @return <code>false</code>, if at least one error or one warning exist
*/
private boolean validateWorkflowAndPlaceholders() {
WorkflowDescriptionValidationUtils.validateWorkflowDescription(wfDescription, true, true);
// placeholder-error
boolean placeholderError = placeholdersPage.validateErrors();
boolean returnValue = !placeholderError && messageStore.isErrorAndWarningsFree();
return returnValue;
}
/**
* @return true if proceed is clicked, false if cancel is clicked
*/
private boolean requestConfirmationForValidationErrorsWarnings() {
WorkflowExecutionWizardValidationDialog dialog = new WorkflowExecutionWizardValidationDialog(getShell(),
messageStore.getMessageMap(), wfDescription, placeholdersPage);
return dialog.open() == 0;
}
private void saveWorkflow() {
wfDescription.addWorkflowNodesAndConnections(disabledWorkflowNodes, disabledConnections);
WorkflowDescriptionPersistenceHandler persistenceHandler = new WorkflowDescriptionPersistenceHandler();
try (ByteArrayOutputStream content = persistenceHandler.writeWorkflowDescriptionToStream(wfDescription)) {
ByteArrayInputStream input = new ByteArrayInputStream(content.toByteArray());
wfFile.setContents(input, // the file content
true, // keep saving, even if IFile is out of sync with the
// Workspace
false, // dont keep history
null); // progress monitor
wfFile.getProject().refreshLocal(IProject.DEPTH_INFINITE, new NullProgressMonitor());
} catch (CoreException | IOException e) {
MessageDialog.openError(getShell(), "Error when Saving Workflow",
"Failed to save workflow: " + e.getMessage());
LOG.error(StringUtils.format("Failed to save workflow: %s", wfFile.getRawLocation().toOSString()));
}
}
private void executeWorkflow(WorkflowDescription clonedWfDesc) {
DistributedComponentKnowledge compKnowledge = serviceRegistryAccess
.getService(DistributedComponentKnowledgeService.class).getCurrentComponentKnowledge();
try {
WorkflowExecutionUtils.replaceNullNodeIdentifiersWithActualNodeIdentifier(clonedWfDesc, localDefaultNodeId, compKnowledge);
} catch (WorkflowExecutionException e) {
handleWorkflowExecutionError(clonedWfDesc, e);
return;
}
String name = clonedWfDesc.getName();
if (name == null) {
name = Messages.bind(Messages.defaultWorkflowName, wfFile.getName().toString());
}
WorkflowExecutionContextBuilder wfExeCtxBuilder = new WorkflowExecutionContextBuilder(clonedWfDesc);
wfExeCtxBuilder.setInstanceName(name);
wfExeCtxBuilder.setNodeIdentifierStartedExecution(localDefaultNodeId);
if (clonedWfDesc.getAdditionalInformation() != null && !clonedWfDesc.getAdditionalInformation().isEmpty()) {
wfExeCtxBuilder.setAdditionalInformationProvidedAtStart(clonedWfDesc.getAdditionalInformation());
}
WorkflowExecutionContext wfExecutionContext = wfExeCtxBuilder.build();
final WorkflowExecutionInformation wfExeInfo;
try {
wfExeInfo = workflowExecutionService.executeWorkflowAsync(wfExecutionContext);
} catch (WorkflowExecutionException | RemoteOperationException e) {
handleWorkflowExecutionError(clonedWfDesc, e);
return;
}
// before starting the workflow, ensure that the console model is
// initialized
// so that no console output gets lost; this is lazily initialized here
// so the application startup is not slowed down
try {
serviceRegistryAccess.getService(ConsoleRowModelService.class).ensureConsoleCaptureIsInitialized();
} catch (InterruptedException e) {
LOG.error("Failed initialize workflow console capturing for workflow: " + clonedWfDesc.getName(), e);
}
if (inputTabEnabled) {
InputModel.ensureInputCaptureIsInitialized();
}
WorkflowExecutionUtils.removeDisabledWorkflowNodesWithoutNotify(wfExeInfo.getWorkflowDescription());
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
new WorkflowRunEditorAction(wfExeInfo).run();
}
});
}
private void handleWorkflowExecutionError(WorkflowDescription clonedWfDesc, final Throwable e) {
LOG.error("Failed to execute workflow: " + clonedWfDesc.getName(), e);
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
MessageDialog.openError(getShell(), "Workflow Execution Error",
"Failed to execute workflow: " + e.getMessage());
}
});
}
@Override
public void dispose() {
serviceRegistryAccess.dispose();
super.dispose();
}
@Override
public void onDistributedComponentKnowledgeChanged(DistributedComponentKnowledge newState) {
if (PlatformUI.isWorkbenchRunning() && !PlatformUI.getWorkbench().getDisplay().isDisposed()) {
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (workflowPage != null && !workflowPage.getControl().isDisposed()) {
performValidations();
} else {
LogFactory.getLog(getClass()).warn("Got callback (onDistributedComponentKnowledgeChanged)"
+ " but widget(s) already disposed; the listener might not be disposed properly");
}
}
});
}
}
}