/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.gui.workflow.view;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.gef.ContextMenuProvider;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.tools.PanningSelectionTool;
import org.eclipse.gef.ui.parts.GraphicalEditor;
import org.eclipse.help.IContextProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchListener;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import de.rcenvironment.core.component.execution.api.ExecutionControllerException;
import de.rcenvironment.core.component.workflow.api.WorkflowConstants;
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.WorkflowState;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowStateNotificationSubscriber;
import de.rcenvironment.core.component.workflow.execution.spi.SingleWorkflowStateChangeListener;
import de.rcenvironment.core.component.workflow.model.api.Connection;
import de.rcenvironment.core.component.workflow.model.api.Location;
import de.rcenvironment.core.component.workflow.model.api.WorkflowLabel;
import de.rcenvironment.core.gui.workflow.Activator;
import de.rcenvironment.core.gui.workflow.EditorMouseWheelAndKeyListener;
import de.rcenvironment.core.gui.workflow.UncompletedJobsShutdownListener;
import de.rcenvironment.core.gui.workflow.editor.WorkflowEditorHelpContextProvider;
import de.rcenvironment.core.gui.workflow.parts.WorkflowRunEditorEditPartFactory;
import de.rcenvironment.core.gui.workflow.view.Outline.OutlineView;
import de.rcenvironment.core.notification.SimpleNotificationService;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.rpc.RemoteOperationException;
import de.rcenvironment.core.utils.incubator.ServiceRegistry;
/**
* Graphical View for a running workflow instance.
*
* @author Heinrich Wendel
* @author Christian Weiss
* @author Oliver Seebach
*/
public class WorkflowRunEditor extends GraphicalEditor implements ITabbedPropertySheetPageContributor,
SingleWorkflowStateChangeListener {
private static final Log LOG = LogFactory.getLog(WorkflowRunEditor.class);
private SimpleNotificationService sns = new SimpleNotificationService();
private WorkflowStateNotificationSubscriber workflowStateChangeSubscriber;
private TabbedPropertySheetPage tabbedPropertySheetPage;
private GraphicalViewer viewer;
private WorkflowExecutionInformation wfExeInfo;
private ZoomManager zoomManager;
private AtomicBoolean initialWorkflowStateSet = new AtomicBoolean(false);
public WorkflowRunEditor() {
setEditDomain(new DefaultEditDomain(this));
registerWorkbenchListener();
}
private void registerWorkbenchListener() {
PlatformUI.getWorkbench().addWorkbenchListener(new IWorkbenchListener() {
@Override
public boolean preShutdown(IWorkbench workbench, boolean arg1) {
// Close Workflow Run Editor programmatically on shutdown
WorkflowRunEditor.this.getSite().getPage().closeEditor(WorkflowRunEditor.this, false);
return true;
}
@Override
public void postShutdown(IWorkbench workbench) {}
});
}
public boolean isWorkflowExecutionInformationSet() {
return wfExeInfo != null;
}
@Override
protected void configureGraphicalViewer() {
super.configureGraphicalViewer();
getGraphicalViewer().setEditPartFactory(new WorkflowRunEditorEditPartFactory());
getGraphicalViewer().getControl().setBackground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY));
}
@Override
public Object getAdapter(@SuppressWarnings("rawtypes") Class type) {
if (type == IPropertySheetPage.class) {
if (tabbedPropertySheetPage == null || tabbedPropertySheetPage.getControl() == null
|| tabbedPropertySheetPage.getControl().isDisposed()) {
tabbedPropertySheetPage = new TabbedPropertySheetPage(this);
}
return tabbedPropertySheetPage;
} else if (type == IContextProvider.class) {
return new WorkflowEditorHelpContextProvider(viewer);
} else if (type == IContentOutlinePage.class) {
return new OutlineView(getGraphicalViewer());
}
return super.getAdapter(type);
}
private void updateTitle(String title) {
setPartName(title);
}
/**
* Retrieves the current state of the workflow and adds it to the title of the view.
*
* @param workflowState new {@link WorkflowState}
*/
public void updateTitle(WorkflowState workflowState) {
updateTitle(wfExeInfo.getInstanceName() + ": " + workflowState.getDisplayName());
}
/**
* Retrieves the current state of the workflow and sets the icon of the view accordingly.
*
* @param state new {@link WorkflowState}
*/
public void updateTabIcon(WorkflowState state) {
Image stateImage = Activator.getInstance().getImageRegistry().get(state.name());
if (stateImage != null) {
setTitleImage(stateImage);
} else {
setTitleImage(Activator.getInstance().getImageRegistry().get(WorkflowState.UNKNOWN.name()));
}
}
/**
* Called externally from WorkflowListView to set a new workflow.
*
* @param wei The workflow.
*/
public void setWorkflowExecutionInformation(final WorkflowExecutionInformation wei) {
this.wfExeInfo = wei;
// FIXME: separate workflow execution information from workflow layout information to allow changes on gui side even if
// backward compatibility is required - two following for-loops should be removed as soon as issue #0011902 is resolved or with
// 7.0 as backward compatibility is not needed any longer -seid_do, April 2015
if ((!wfExeInfo.getWorkflowDescription().getConnections().isEmpty()
&& wfExeInfo.getWorkflowDescription().getConnections().get(0).getBendpoints() == null)
|| (!wfExeInfo.getWorkflowDescription().getWorkflowLabels().isEmpty()
&& wfExeInfo.getWorkflowDescription().getWorkflowLabels().get(0).getLabelPosition() == null)) {
wfExeInfo.getWorkflowDescription().setWorkflowLabels(new ArrayList<WorkflowLabel>());
for (Connection connection : wfExeInfo.getWorkflowDescription().getConnections()) {
connection.setBendpoints(new ArrayList<Location>());
}
MessageDialog.openInformation(Display.getDefault().getActiveShell(), "Labels/custom connection paths",
"Labels and/or custom connection paths cannot be displayed, as the controller was executed on an RCE instance <= 6.1, "
+ "which neither supports labels nor custom connection paths.");
}
// set the model of the editor
viewer.setContents(wei);
workflowStateChangeSubscriber = new WorkflowStateNotificationSubscriber(WorkflowRunEditor.this,
wei.getExecutionIdentifier());
Job job = new Job(StringUtils.format("Initializing state of workflow '%s'", wei.getInstanceName())) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
sns.subscribe(WorkflowConstants.STATE_NOTIFICATION_ID + wfExeInfo.getExecutionIdentifier(),
workflowStateChangeSubscriber, wfExeInfo.getNodeId());
} catch (RemoteOperationException e1) {
// TODO review: how to react on this?
LOG.error("Failed to subscribe for workflow state changes: " + e1.getMessage());
return Status.CANCEL_STATUS; // should be the closest to the "old" RTE behavior for now
}
if (!initialWorkflowStateSet.get()) {
WorkflowExecutionService wfExecutionService = ServiceRegistry.createAccessFor(WorkflowRunEditor.this)
.getService(WorkflowExecutionService.class);
try {
WorkflowState workflowState = wfExecutionService.getWorkflowState(
wfExeInfo.getExecutionIdentifier(), wfExeInfo.getNodeId());
synchronized (WorkflowRunEditor.this) {
if (!initialWorkflowStateSet.get()) {
onWorkflowStateChanged(workflowState);
}
}
} catch (ExecutionControllerException | RemoteOperationException e) {
// TODO review: how to react on this?
LOG.error("Failed to subscribe for workflow state changes: " + e.getMessage());
return Status.CANCEL_STATUS; // should be the closest to the "old" RTE behavior for now
}
}
return Status.OK_STATUS;
}
};
job.schedule();
}
@Override
public void dispose() {
Job job = new Job("Unsubscribing from workflow host") {
@Override
protected IStatus run(IProgressMonitor monitor) {
if (wfExeInfo != null) {
try {
sns.unsubscribe(WorkflowConstants.STATE_NOTIFICATION_ID + wfExeInfo.getExecutionIdentifier(),
workflowStateChangeSubscriber, wfExeInfo.getNodeId());
} catch (RemoteOperationException e) {
LOG.error("Failed to unsubscribe workflow execution view from workflow host: " + e.getMessage());
return Status.CANCEL_STATUS;
}
}
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return family == UncompletedJobsShutdownListener.MUST_BE_COMPLETED_ON_SHUTDOWN_JOB_FAMILY;
}
};
job.setSystem(true);
job.schedule();
super.dispose();
}
@Override
protected void initializeGraphicalViewer() {
viewer = getGraphicalViewer();
viewer.setRootEditPart(new ScalableFreeformRootEditPart());
final ContextMenuProvider cmProvider = new WorkflowRunEditorContextMenuProvider(viewer);
viewer.setContextMenu(cmProvider);
tabbedPropertySheetPage = new TabbedPropertySheetPage(this);
zoomManager = ((ScalableFreeformRootEditPart) getGraphicalViewer().getRootEditPart()).getZoomManager();
EditorMouseWheelAndKeyListener editorMouseWheelKeyListener = new EditorMouseWheelAndKeyListener(zoomManager);
viewer.getControl().addMouseWheelListener(editorMouseWheelKeyListener);
viewer.getControl().addKeyListener(editorMouseWheelKeyListener);
viewer.getEditDomain().setDefaultTool(new PanningSelectionTool());
viewer.getEditDomain().loadDefaultTool();
setTitleImage(Activator.getInstance().getImageRegistry().get(WorkflowState.UNKNOWN.name()));
}
@Override
public boolean isDirty() {
return false;
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
@Override
public String getTitleToolTip() {
return "";
}
@Override
public void doSave(IProgressMonitor monitor) {}
@Override
public String getContributorId() {
return getSite().getId();
}
@Override
public synchronized void onWorkflowStateChanged(final WorkflowState newState) {
initialWorkflowStateSet.set(true);
if (newState == WorkflowState.DISPOSING || newState == WorkflowState.DISPOSED) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
WorkflowRunEditor.this.getSite().getPage().closeEditor(WorkflowRunEditor.this, false);
}
});
} else {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
updateTitle(newState);
updateTabIcon(newState);
}
});
}
}
@Override
public void onWorkflowNotAliveAnymore(String errorMessage) {
onWorkflowStateChanged(WorkflowState.UNKNOWN);
}
}