/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.gui.workflow.execute;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
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.UUID;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
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.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.part.FileEditorInput;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionService;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowFileException;
import de.rcenvironment.core.component.workflow.model.api.WorkflowDescription;
import de.rcenvironment.core.gui.workflow.GUIWorkflowDescriptionLoaderCallback;
import de.rcenvironment.core.gui.workflow.editor.WorkflowEditor;
import de.rcenvironment.core.utils.common.CrossPlatformFilenameUtils;
import de.rcenvironment.core.utils.common.InvalidFilenameException;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.incubator.ServiceRegistry;
/**
* Opens the {@link WorkflowExecutionWizard}.
*
* @author Christian Weiss
* @author Sascha Zur
* @author Doreen Seider
*/
public class ShowWorkflowExecutionWizardHandler extends AbstractHandler {
private static final String DUPLICATE_ID_WARNING_MSG = "Could not determine duplicate WF ids";
private static final String GETTING_ATTR_WARNING_MSG = " - failed to get file attributes for: ";
private static final String NODES = "nodes";
private static final String IDENTIFIER = "identifier";
private static final Pattern WORKFLOW_FILENAME_PATTERN = Pattern.compile("^.*\\.wf$");
private static final Log LOGGER = LogFactory.getLog(ShowWorkflowExecutionWizardHandler.class);
private final ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();
private WorkflowExecutionService workflowExecutionService;
public ShowWorkflowExecutionWizardHandler() {
workflowExecutionService = ServiceRegistry.createPublisherAccessFor(this).getService(WorkflowExecutionService.class);
}
@Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
final boolean wfFileOpenedInEditor;
IFile wfFile = tryToGetWorkflowFileFromProjectExplorer(event);
if (wfFile == null) {
wfFile = tryToGetWorkflowFileFromWorkflowEditor();
if (wfFile == null) {
return null;
} else {
wfFileOpenedInEditor = true;
}
} else if (!checkForOpenRelatedDirtyWorkflowEditor(wfFile)) {
return null;
} else {
wfFileOpenedInEditor = false;
}
searchAndReplaceDuplicateIDs(wfFile);
// Check if the filename is valid. If this is not the case, execution may fail.
try {
CrossPlatformFilenameUtils.throwExceptionIfFilenameNotValid(wfFile.getName());
} catch (InvalidFilenameException e) {
MessageDialog.open(MessageDialog.WARNING, null, "Error during Workflow preparation",
"The workflow's file name is not valid on all supported platforms. Please rename the workflow.", SWT.NONE);
return null;
}
if (wfFileOpenedInEditor) {
openWorkflowExecutionWizardWithWfFileFromEditor(wfFile);
} else {
openWorkflowExecutionWizardWithWfFileFromProjectExplorer(wfFile);
}
return null;
}
private void openWorkflowExecutionWizardWithWfFileFromEditor(IFile wfFile) {
WorkflowDescription wfDescription = null;
try {
File wfFile2 = new File(wfFile.getLocation().toOSString());
if (wfFile2.exists()) {
wfDescription = workflowExecutionService.loadWorkflowDescriptionFromFile(
wfFile2, new GUIWorkflowDescriptionLoaderCallback());
}
} catch (RuntimeException | WorkflowFileException e) {
// caught and only logged as an error dialog already pops up if an error occur
LogFactory.getLog(getClass()).error("Failed to load workflow: " + wfFile.getLocation().toOSString(), e);
return;
}
if (wfDescription != null) {
openWorkflowExecutionWizard(wfFile, wfDescription);
} else {
MessageDialog.open(MessageDialog.WARNING, null, "Error Loading Workflow",
"The workflow file could not be found.\nMaybe it was renamed?", SWT.NONE);
}
}
private void openWorkflowExecutionWizardWithWfFileFromProjectExplorer(IFile wfFile) {
final IFile finalWfFile = wfFile;
Job job = new Job("Executing workflow") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
monitor.beginTask("Loading workflow components", 2);
monitor.worked(1);
final WorkflowDescription wfDescription = workflowExecutionService
.loadWorkflowDescriptionFromFileConsideringUpdates(
new File(finalWfFile.getRawLocation().toOSString()), new GUIWorkflowDescriptionLoaderCallback());
monitor.worked(1);
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
openWorkflowExecutionWizard(finalWfFile, wfDescription);
}
});
} catch (final WorkflowFileException e) {
// caught and only logged as an error dialog already pops up if an error occur
LogFactory.getLog(getClass()).error("Failed to load workflow: " + finalWfFile.getRawLocation().toOSString(), e);
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
// do not use Display.getDefault().getActiveShell() as this might return
// the progress monitor dialog
MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
"Workflow File Error", e.getMessage());
}
});
} finally {
monitor.done();
}
return Status.OK_STATUS;
};
};
job.setUser(true);
job.schedule();
}
private void openWorkflowExecutionWizard(IFile wfFile, WorkflowDescription wfDescription) {
final Wizard wfExecutionWizard = new WorkflowExecutionWizard(wfFile, wfDescription);
// do not use Display.getDefault().getActiveShell() as this might return the progress
// monitor dialog
final WorkflowWizardDialog wizardDialog = new WorkflowWizardDialog(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), wfExecutionWizard);
wizardDialog.setBlockOnOpen(false);
wizardDialog.open();
}
private boolean checkForOpenRelatedDirtyWorkflowEditor(IFile wfFile) {
// Compares the project explorer selection with dirty editors and triggers saveChangesDialog
// if selection is dirty
IEditorReference[] currentEditors = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getEditorReferences();
for (IEditorReference editor : currentEditors) {
IEditorPart editorPart = (IEditorPart) editor.getPart(true);
if (editorPart instanceof WorkflowEditor) {
WorkflowEditor workflowEditor = (WorkflowEditor) editorPart;
IFile editorFile = ((FileEditorInput) workflowEditor.getEditorInput()).getFile();
if (wfFile.getProject().equals(editorFile.getProject())) {
if (wfFile.getName().equals(editorPart.getTitle()) && editorPart.isDirty()) {
return saveChangesDialog(editorPart);
}
}
}
}
return true;
}
private IFile tryToGetWorkflowFileFromWorkflowEditor() {
final IWorkbenchPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
if (part instanceof IEditorPart) {
IEditorPart editor = (IEditorPart) part;
if (editor instanceof WorkflowEditor) {
WorkflowEditor workflowEditor = (WorkflowEditor) editor;
IEditorInput input = workflowEditor.getEditorInput();
if (input instanceof FileEditorInput) {
if (!workflowEditor.isDirty() || saveChangesDialog(workflowEditor)) {
return ((FileEditorInput) input).getFile();
}
} else if (input instanceof FileStoreEditorInput) {
MessageDialog.openInformation(part.getSite().getShell(), "Workflow Run",
"Workflow file can not be executed. Please put the workflow into a project of your workspace first.\n\n"
+ "Drag the workflow from the file system into the project explorer to a project of your choice. "
+ "You can decide to either copy or only link it.\n\nIf you don't have a project yet, "
+ "create one first via File->New->Project->General.");
}
}
}
return null;
}
private IFile tryToGetWorkflowFileFromProjectExplorer(final ExecutionEvent event) {
final ISelection selection = HandlerUtil.getCurrentSelection(event);
if (selection instanceof IStructuredSelection) {
final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
for (Iterator<?> iter = structuredSelection.iterator(); iter.hasNext();) {
Object next = iter.next();
if (!(next instanceof IFile)) {
continue;
}
final IFile file = (IFile) next;
String filename = file.getName();
if (!WORKFLOW_FILENAME_PATTERN.matcher(filename).matches()) {
continue;
}
return file;
}
}
return null;
}
private boolean saveChangesDialog(IEditorPart editor) {
final IWorkbenchPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
if (MessageDialog.openConfirm(part.getSite().getShell(), Messages.askToSaveUnsavedEditorChangesTitle,
StringUtils.format(Messages.askToSaveUnsavedEditorChangesMessage, editor.getTitle()))) {
editor.doSave(null);
return true;
}
return false;
}
private void searchAndReplaceDuplicateIDs(IFile workflowFile) {
String absoluteFilePath = workflowFile.getLocation().toString();
Set<IResource> wfsWithDuplicateId = searchForDuplicateWFIdentifier(absoluteFilePath);
if (wfsWithDuplicateId != null && !wfsWithDuplicateId.isEmpty()) {
try {
Path orig = Paths.get(absoluteFilePath);
BasicFileAttributeView origAttrView = Files.getFileAttributeView(orig, BasicFileAttributeView.class);
if (origAttrView == null) {
LOGGER.warn(DUPLICATE_ID_WARNING_MSG + GETTING_ATTR_WARNING_MSG + absoluteFilePath);
return;
}
BasicFileAttributes attrOrig = origAttrView.readAttributes();
for (IResource duplicate : wfsWithDuplicateId) {
Path duplicatePath = Paths.get(duplicate.getLocationURI());
BasicFileAttributeView attrViewDuplicate = Files.getFileAttributeView(duplicatePath, BasicFileAttributeView.class);
if (attrViewDuplicate == null) {
LOGGER.warn(DUPLICATE_ID_WARNING_MSG + GETTING_ATTR_WARNING_MSG + absoluteFilePath);
continue;
}
BasicFileAttributes attrDuplicate = attrViewDuplicate.readAttributes();
// Comparing the creation time is buggy on linux machines.
// It returns the modification time which can be the same as in the original and
// thus the original file is modified, doesn't find its placeholder and
// falls back to an old placeholder
// The bug is fixed in java 8:
// http://hg.openjdk.java.net/jdk8/jdk8/jdk/rev/296c9ec816c6
if (attrOrig.creationTime().compareTo(attrDuplicate.creationTime()) > 0) {
replaceIdentifierInWorkflowFile(absoluteFilePath);
workflowFile.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
} else {
replaceIdentifierInWorkflowFile(duplicate.getLocation().toString());
duplicate.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
}
}
} catch (CoreException e) {
LOGGER.warn(DUPLICATE_ID_WARNING_MSG, e);
} catch (IOException e) {
LOGGER.warn(DUPLICATE_ID_WARNING_MSG, e);
}
}
}
@SuppressWarnings("unchecked")
private void replaceIdentifierInWorkflowFile(String filePath) {
try {
File wfToEdit = new File(filePath);
Map<String, Object> wfContent = mapper.readValue(wfToEdit, new HashMap<String, Object>().getClass());
String wfContentString = FileUtils.readFileToString(wfToEdit);
String idWorkflow = (String) wfContent.get(IDENTIFIER);
wfContentString = wfContentString.replace(idWorkflow, UUID.randomUUID().toString());
List<Map<String, Object>> nodes = (List<Map<String, Object>>) wfContent.get(NODES);
if (nodes != null) {
for (Map<String, Object> node : nodes) {
String nodeID = (String) node.get(IDENTIFIER);
wfContentString = wfContentString.replaceAll(nodeID, UUID.randomUUID().toString());
}
}
FileUtils.write(wfToEdit, wfContentString);
} catch (IOException e) {
LOGGER.warn(DUPLICATE_ID_WARNING_MSG, e);
}
}
private Set<IResource> searchForDuplicateWFIdentifier(String absoluteFilePath) {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
Set<IResource> duplicates = new HashSet<IResource>();
try {
File file = new File(absoluteFilePath);
if (file.exists()) {
@SuppressWarnings("unchecked") Map<String, Object> wfContent =
mapper.readValue(file, new HashMap<String, Object>().getClass());
for (IProject p : root.getProjects()) {
if (p.isOpen()) {
try {
for (IResource res : p.members()) {
if (res instanceof IFile && WORKFLOW_FILENAME_PATTERN
.matcher(((IFile) res).getName()).matches()) {
IResource possibleDuplicate = checkFileForDuplicateID(wfContent, absoluteFilePath, res);
if (possibleDuplicate != null && !absoluteFilePath.equals(possibleDuplicate.getLocation().toString())) {
duplicates.add(possibleDuplicate);
}
}
if (res instanceof IFolder) {
Set<IResource> possibleDuplicate = checkFolderForDuplicateId(wfContent, absoluteFilePath, res);
if (possibleDuplicate != null) {
duplicates.addAll(possibleDuplicate);
}
}
}
} catch (CoreException e) {
LOGGER.warn(DUPLICATE_ID_WARNING_MSG, e);
} catch (IOException e) {
LOGGER.warn(DUPLICATE_ID_WARNING_MSG, e);
}
}
}
}
} catch (IOException e) {
LOGGER.warn(DUPLICATE_ID_WARNING_MSG, e);
}
return duplicates;
}
private Set<IResource> checkFolderForDuplicateId(Map<String, Object> wfContent, String absoluteFilePath, IResource res)
throws JsonParseException,
JsonMappingException, IOException, CoreException {
Set<IResource> possibleDuplicates = new HashSet<IResource>();
for (IResource resource : ((IFolder) res).members()) {
if (resource instanceof IFolder) {
Set<IResource> possibleDuplicate = checkFolderForDuplicateId(wfContent, absoluteFilePath, resource);
if (possibleDuplicate != null) {
possibleDuplicates.addAll(possibleDuplicate);
}
} else if (resource instanceof IFile
&& WORKFLOW_FILENAME_PATTERN.matcher(((IFile) resource).getName()).matches()) {
IResource possibleDuplicate = checkFileForDuplicateID(wfContent, absoluteFilePath, resource);
if (possibleDuplicate != null && !absoluteFilePath.equals(possibleDuplicate.getLocation().toString())) {
possibleDuplicates.add(possibleDuplicate);
}
}
}
return possibleDuplicates;
}
@SuppressWarnings("unchecked")
private IResource checkFileForDuplicateID(Map<String, Object> wfContent, String absoluteFilePath, IResource res) {
try {
File file = new File(res.getLocation().toString());
if (file.exists()) {
Map<String, Object> wfContentExisting =
mapper.readValue(file, new HashMap<String, Object>().getClass());
if (!absoluteFilePath.equals(res.getLocation().toString())
&& wfContent.get(IDENTIFIER).equals(wfContentExisting.get(IDENTIFIER))) {
return res;
}
}
} catch (IOException e) {
LOGGER.warn(StringUtils.format("Skipped corrupted wf file: %s (%s)", e.getMessage(), res.getLocation().toString()));
}
return null;
}
/**
* {@link WizardDialog} sub type to be adapted to the needs of a workflow execution.
*
* @author Christian Weiss
*/
private static final class WorkflowWizardDialog extends WizardDialog {
/**
* The Constructor.
*
* @param activeShell the parent shell
* @param workflowExecutionWizard the wizard this dialog is working on
*/
WorkflowWizardDialog(Shell activeShell, Wizard workflowExecutionWizard) {
super(activeShell, workflowExecutionWizard);
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
super.createButtonsForButtonBar(parent);
Button okButton = getButton(IDialogConstants.FINISH_ID);
if (okButton != null) {
okButton.setText(Messages.executionWizardFinishButtonLabel);
}
}
}
}