| /******************************************************************************* |
| * Copyright (C) 2007 The University of Manchester |
| * |
| * Modifications to the initial code base are copyright of their |
| * respective authors, or their employers as appropriate. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public License |
| * as published by the Free Software Foundation; either version 2.1 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| ******************************************************************************/ |
| package net.sf.taverna.t2.reference.ui; |
| |
| import static com.hp.hpl.jena.sparql.graph.GraphFactory.makeDefaultModel; |
| import static java.awt.BorderLayout.CENTER; |
| import static java.awt.BorderLayout.EAST; |
| import static java.awt.BorderLayout.SOUTH; |
| import static java.awt.BorderLayout.WEST; |
| import static javax.swing.BoxLayout.X_AXIS; |
| import static javax.swing.JOptionPane.ERROR_MESSAGE; |
| import static javax.swing.JOptionPane.showMessageDialog; |
| import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.closeIcon; |
| import static org.apache.jena.riot.Lang.TURTLE; |
| import static org.apache.jena.riot.RDFLanguages.contentTypeToLang; |
| |
| import java.awt.BorderLayout; |
| import java.awt.Dimension; |
| import java.awt.GridLayout; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.WindowAdapter; |
| import java.awt.event.WindowEvent; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.nio.file.Path; |
| 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 javax.swing.AbstractAction; |
| import javax.swing.Action; |
| import javax.swing.BoxLayout; |
| import javax.swing.ImageIcon; |
| import javax.swing.JButton; |
| import javax.swing.JFrame; |
| import javax.swing.JPanel; |
| import javax.swing.JScrollPane; |
| import javax.swing.JTabbedPane; |
| import javax.swing.JToolBar; |
| import javax.swing.border.EmptyBorder; |
| import javax.swing.border.TitledBorder; |
| |
| import net.sf.taverna.t2.lang.observer.Observable; |
| import net.sf.taverna.t2.lang.observer.Observer; |
| import net.sf.taverna.t2.lang.ui.DialogTextArea; |
| import net.sf.taverna.t2.reference.ui.referenceactions.ReferenceActionSPI; |
| import net.sf.taverna.t2.workbench.edits.EditManager; |
| import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent; |
| import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent; |
| import net.sf.taverna.t2.workbench.file.FileManager; |
| import net.sf.taverna.t2.workbench.file.events.ClosedDataflowEvent; |
| import net.sf.taverna.t2.workbench.file.events.FileManagerEvent; |
| import net.sf.taverna.t2.workbench.report.ReportManager; |
| import net.sf.taverna.t2.workbench.ui.Workbench; |
| |
| import org.apache.batik.swing.JSVGCanvas; |
| import org.apache.jena.riot.Lang; |
| import org.apache.jena.riot.RDFDataMgr; |
| import org.apache.jena.riot.RIOT; |
| import org.apache.log4j.Logger; |
| import org.purl.wf4ever.robundle.Bundle; |
| |
| import uk.org.taverna.configuration.database.DatabaseConfiguration; |
| import uk.org.taverna.databundle.DataBundles; |
| import uk.org.taverna.scufl2.api.annotation.Annotation; |
| import uk.org.taverna.scufl2.api.common.Scufl2Tools; |
| import uk.org.taverna.scufl2.api.common.URITools; |
| import uk.org.taverna.scufl2.api.common.WorkflowBean; |
| import uk.org.taverna.scufl2.api.container.WorkflowBundle; |
| import uk.org.taverna.scufl2.api.core.Workflow; |
| import uk.org.taverna.scufl2.api.port.InputWorkflowPort; |
| |
| import com.hp.hpl.jena.rdf.model.Model; |
| import com.hp.hpl.jena.rdf.model.Property; |
| import com.hp.hpl.jena.rdf.model.Resource; |
| |
| /** |
| * A simple workflow launch window, uses a tabbed layout to display a set of named RegistrationPanel |
| * instances, and a 'run workflow' button. Also shows a pane contining a picture of the workflow, |
| * the author and the description. |
| * We use one WorkflowLaunchWindow per workflow, multiple runs of the same workflow get the same |
| * window. |
| * |
| * @author Tom Oinn |
| * @author David Withers |
| * @author Stian Soiland-Reyes |
| * @author Alan R Williams |
| * @author Alex Nenadic |
| */ |
| @SuppressWarnings("serial") |
| public abstract class WorkflowLaunchWindow extends JFrame { |
| private static final Logger logger = Logger.getLogger(WorkflowLaunchWindow.class); |
| private static final String LAUNCH_WORKFLOW = "Run workflow"; |
| private static final ImageIcon launchIcon = new ImageIcon( |
| WorkflowLaunchWindow.class.getResource("/icons/start_task.gif")); |
| private static final ImageIcon addTextIcon = new ImageIcon( |
| WorkflowLaunchWindow.class.getResource("/icons/addtext_co.gif")); |
| private static final String NO_WORKFLOW_DESCRIPTION = "No description"; |
| private static final String NO_WORKFLOW_AUTHOR = "No author"; |
| |
| /** |
| * An action enabled when all inputs are enabled and used to trigger the |
| * {@link #handleLaunch(Bundle)} method |
| */ |
| private Action launchAction; |
| /** |
| * A map of input port names to input registration panels (from the previous |
| * run of the same workflow, if any) |
| */ |
| private Map<String, RegistrationPanel> inputPanelMap = new HashMap<>(); |
| /** A pane holding various tabs for workflow input ports */ |
| private JTabbedPane tabsPane; |
| private Workflow workflow; |
| private DialogTextArea workflowDescriptionArea; |
| private DialogTextArea workflowAuthorArea; |
| private JSVGCanvas createWorkflowGraphic; |
| /** |
| * Whether the original workflow has been modified in the design perspective |
| * so we know to refresh this dialog |
| */ |
| private boolean workflowModified = false; |
| private FileManager fileManager; |
| /** Observer of workflow closing events so we can dispose off the window */ |
| private FileManagerObserver fileManagerObserver = new FileManagerObserver(); |
| private EditManager editManager; |
| private EditManagerObserver editManagerObserver = new EditManagerObserver(); |
| private JPanel overallPanel; |
| private JPanel workflowPart; |
| private JPanel portsPart; |
| @SuppressWarnings("unused") |
| private final Workbench workbench; |
| @SuppressWarnings("unused") |
| private final ReportManager reportManager; |
| private final List<ReferenceActionSPI> referenceActionSPIs; |
| private final DatabaseConfiguration databaseConfiguration; |
| private final Scufl2Tools scufl2Tools = new Scufl2Tools(); |
| private final URITools uriTools = new URITools(); |
| |
| public WorkflowLaunchWindow(Workflow workflow, EditManager editManager, |
| FileManager fileManager, ReportManager reportManager, |
| Workbench workbench, List<ReferenceActionSPI> referenceActionSPIs, |
| DatabaseConfiguration databaseConfiguration) { |
| super(); |
| // Initialize RIOT reader |
| RIOT.register(); |
| |
| this.workflow = workflow; |
| this.editManager = editManager; |
| this.fileManager = fileManager; |
| this.reportManager = reportManager; |
| this.workbench = workbench; |
| this.referenceActionSPIs = referenceActionSPIs; |
| this.databaseConfiguration = databaseConfiguration; |
| |
| initComponents(); |
| |
| // Handle refreshing the frame when it receives focus |
| addWindowFocusListener(new WindowAdapter() { |
| @Override |
| public void windowGainedFocus(WindowEvent e) { |
| if (workflowModified) { |
| // Clear all previous components |
| getContentPane().removeAll(); |
| |
| // Redraw the window |
| initComponents(); |
| |
| overallPanel.revalidate(); |
| overallPanel.repaint(); |
| |
| workflowModified = false; |
| } |
| } |
| }); |
| |
| // Handle window closing |
| addWindowListener(new WindowAdapter() { |
| @Override |
| public void windowClosing(WindowEvent winEvt) { |
| handleCancel(); // do not dispose the window, just hide it |
| } |
| }); |
| |
| // Start observing workflow closing events on File Manager |
| fileManager.addObserver(fileManagerObserver); |
| |
| // Start observing edit workflow events on Edit Manager |
| editManager.addObserver(editManagerObserver); |
| } |
| |
| /** |
| * Set the title of the window to contain the workflow name and its file/url |
| * location so that users can easily identify which workflow is being run. |
| * |
| * @param title |
| */ |
| private void setWindowTitle(String title) { |
| String windowTitle = "Input values for "; |
| if (title != null && !title.isEmpty()) |
| windowTitle += "'" + title + "' "; |
| else |
| // Fall back to its name |
| windowTitle += "'" + workflow.getName() + "' "; |
| |
| Object workflowLocation = fileManager.getDataflowSource(workflow.getParent()); |
| windowTitle += (workflowLocation == null) ? "" : "from " + workflowLocation.toString(); |
| setTitle(windowTitle); |
| } |
| |
| /** |
| * Draw the components of the frame. |
| */ |
| public void initComponents() { |
| workflowPart = new JPanel(new GridLayout(3, 1)); |
| portsPart = new JPanel(new BorderLayout()); |
| |
| createWorkflowGraphic = createWorkflowGraphic(workflow); |
| createWorkflowGraphic.setBorder(new TitledBorder("Diagram")); |
| |
| workflowPart.add(new JScrollPane(createWorkflowGraphic)); |
| |
| workflowDescriptionArea = new DialogTextArea(NO_WORKFLOW_DESCRIPTION, 5, 40); |
| workflowDescriptionArea.setBorder(new TitledBorder("Workflow description")); |
| workflowDescriptionArea.setEditable(false); |
| workflowDescriptionArea.setLineWrap(true); |
| workflowDescriptionArea.setWrapStyleWord(true); |
| |
| workflowPart.add(new JScrollPane(workflowDescriptionArea)); |
| |
| workflowAuthorArea = new DialogTextArea(NO_WORKFLOW_AUTHOR, 1, 40); |
| workflowAuthorArea.setBorder(new TitledBorder("Workflow author")); |
| workflowAuthorArea.setEditable(false); |
| workflowAuthorArea.setLineWrap(true); |
| workflowAuthorArea.setWrapStyleWord(true); |
| |
| workflowPart.add(new JScrollPane(workflowAuthorArea)); |
| |
| launchAction = new AbstractAction(LAUNCH_WORKFLOW, launchIcon) { |
| @Override |
| public void actionPerformed(ActionEvent ae) { |
| // First of all - is the workflow valid? |
| // TODO convert to Scufl2 validation |
| |
| // if (!CheckWorkflowStatus.checkWorkflow(dataflowOriginal, workbench, editManager, |
| // fileManager, reportManager)) { |
| // setVisible(false); |
| // return; |
| // } |
| |
| /* |
| * Check if user had entered input values for all input ports - |
| * otherwise there is no point in attempting to run the workflow |
| */ |
| for (InputWorkflowPort input : workflow.getInputPorts()) { |
| RegistrationPanel registrationPanel = inputPanelMap.get(input.getName()); |
| Object userInput = registrationPanel.getUserInput(); |
| if (userInput == null) { |
| showMessageDialog( |
| WorkflowLaunchWindow.this, |
| "You have not provided input values for all workflow inputs", |
| "Workflow input value error", ERROR_MESSAGE); |
| // exit |
| return; |
| } |
| } |
| setState(ICONIFIED); |
| |
| try { |
| Bundle inputDataBundle = createInputDataBundle(); |
| handleLaunch(inputDataBundle); |
| } catch (IOException e) { |
| showMessageDialog(WorkflowLaunchWindow.this, |
| "An error occurred while creating input values\n" |
| + e.getMessage(), "Error creating inputs", |
| ERROR_MESSAGE); |
| return; |
| } |
| } |
| }; |
| |
| WorkflowBundle workflowBundle = workflow.getParent(); |
| |
| Model annotations = annotationsForBean(workflowBundle, workflow); |
| Resource bean = annotations.getResource(uriTools.uriForBean(workflow) |
| .toASCIIString()); |
| |
| String title = null; |
| Property titleProp = annotations |
| .createProperty("http://purl.org/dc/terms/title"); |
| if (bean.hasProperty(titleProp)) |
| title = bean.getProperty(titleProp).getString(); |
| setWindowTitle(title); |
| |
| Property descProp = annotations |
| .createProperty("http://purl.org/dc/terms/description"); |
| if (bean.hasProperty(descProp)) |
| setWorkflowDescription(bean.getProperty(descProp).getString()); |
| |
| Property creatorProp = annotations |
| .createProperty("http://purl.org/dc/elements/1.1/creator"); |
| if (bean.hasProperty(creatorProp)) |
| setWorkflowAuthor(bean.getProperty(creatorProp).getString()); |
| |
| Action useExamplesAction = new AbstractAction("Use examples", addTextIcon) { |
| @Override |
| public void actionPerformed(ActionEvent arg0) { |
| List<InputWorkflowPort> inputPorts = new ArrayList<>( |
| workflow.getInputPorts()); |
| /* |
| * Create tabs for input ports (but only for the one that are |
| * connected!) |
| */ |
| for (InputWorkflowPort inputPort : inputPorts) { |
| RegistrationPanel rp = inputPanelMap.get(inputPort |
| .getName()); |
| Object example = rp.getExample(); |
| if (example != null && inputPort.getDepth() == 0 |
| && rp.getValue() == null) |
| rp.setValue(example); |
| } |
| } |
| }; |
| |
| JButton useExamplesButton = new JButton(useExamplesAction); |
| useExamplesButton |
| .setToolTipText("Use the example value (if any) for ports that you have not set a value for"); |
| // Construct tool bar |
| JToolBar toolBar = new JToolBar(); |
| toolBar.setFloatable(false); |
| toolBar.add(useExamplesButton); |
| toolBar.add(new JButton(launchAction)); |
| toolBar.add(new JButton(new AbstractAction("Cancel", closeIcon) { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| handleCancel(); |
| } |
| })); |
| |
| JToolBar loadButtonsBar = new JToolBar(); |
| loadButtonsBar.setFloatable(false); |
| for (ReferenceActionSPI spi : referenceActionSPIs) { |
| ReferenceActionSPI action = (ReferenceActionSPI) spi.getAction(); |
| action.setInputPanelMap(inputPanelMap); |
| JButton loadButton = new JButton((AbstractAction) action); |
| loadButtonsBar.add(loadButton); |
| } |
| |
| JPanel toolBarPanel = new JPanel(new BorderLayout()); |
| toolBarPanel.add(loadButtonsBar, WEST); |
| toolBarPanel.add(toolBar, EAST); |
| toolBarPanel.setBorder(new EmptyBorder(5, 10, 5, 20)); |
| portsPart.add(toolBarPanel, SOUTH); |
| |
| /* |
| * Construct tab container - tabs will be populated based on the wf |
| * input ports |
| */ |
| tabsPane = new JTabbedPane(); |
| |
| List<InputWorkflowPort> inputPorts = new ArrayList<>(workflow.getInputPorts()); |
| Set<String> inputNames = new HashSet<>(); |
| |
| /* |
| * Create tabs for input ports (but only for the one that are |
| * connected!) |
| */ |
| for (InputWorkflowPort inputPort : inputPorts) { |
| // Is this input port connected to anything? |
| if (scufl2Tools.datalinksFrom(inputPort).isEmpty()) |
| continue; |
| String portDescription = ""; |
| String portExample = ""; |
| |
| annotations = annotationsForBean(workflowBundle, inputPort); |
| bean = annotations.getResource(uriTools.uriForBean(inputPort) |
| .toASCIIString()); |
| if (bean.hasProperty(descProp)) |
| portDescription = bean.getProperty(descProp).getString(); |
| Property exDataProp = annotations |
| .createProperty("http://biocatalogue.org/attribute/exampleData"); |
| if (bean.hasProperty(exDataProp)) |
| portExample = bean.getProperty(exDataProp).getString(); |
| |
| // add tabs for wf input ports |
| String name = inputPort.getName(); |
| inputNames.add(name); |
| addInput(name, inputPort.getDepth(), portDescription, portExample); |
| } |
| |
| portsPart.add(tabsPane, CENTER); |
| |
| workflowPart.setPreferredSize(new Dimension(300, 500)); |
| portsPart.setPreferredSize(new Dimension(650, 500)); |
| |
| overallPanel = new JPanel(); |
| overallPanel.setLayout(new BoxLayout(overallPanel, X_AXIS)); |
| |
| overallPanel.add(workflowPart); |
| overallPanel.add(portsPart); |
| |
| setLayout(new BorderLayout()); |
| getContentPane().add(new JScrollPane(overallPanel), CENTER); |
| |
| pack(); |
| } |
| |
| private Model annotationsForBean(WorkflowBundle workflowBundle, |
| WorkflowBean bean) { |
| Model model = makeDefaultModel(); |
| for (Annotation annotation : scufl2Tools.annotationsFor(bean, |
| workflowBundle)) { |
| // System.out.println(annotation.getBody()); |
| URI base = uriTools.uriForBean(workflowBundle); |
| URI body = base.resolve(annotation.getBody()); |
| // System.out.println(body); |
| URI path = uriTools.relativePath(workflowBundle.getGlobalBaseURI(), |
| body); |
| // System.out.println(path.getPath()); |
| |
| String mediaType = workflowBundle.getResources() |
| .getResourceEntry(path.getPath()).getMediaType(); |
| Lang lang = contentTypeToLang(mediaType); |
| if (lang == null) |
| // Safe fallback |
| lang = TURTLE; |
| // System.out.println(mediaType); |
| try (InputStream inputStream = workflowBundle.getResources() |
| .getResourceAsInputStream(path.getPath())) { |
| RDFDataMgr.read(model, inputStream, body.toASCIIString(), lang); |
| // model.read(inputStream, body.toASCIIString(), mediaType); |
| } catch (IOException e) { |
| logger.warn("Can't read " + body, e); |
| continue; |
| } |
| } |
| return model; |
| } |
| |
| /** |
| * User clicked the cancel button. |
| */ |
| public void cancelPressed() { |
| this.setVisible(true); |
| } |
| |
| /** |
| * Creates an SVGCanvas loaded with the SVGDocument for the Dataflow. |
| * |
| * @param dataflow |
| * @return |
| */ |
| private JSVGCanvas createWorkflowGraphic(Workflow worklfow) { |
| JSVGCanvas svgCanvas = new JSVGCanvas(); |
| //SVGGraphController graphController = GraphViewComponent.graphControllerMap.get(workflow); |
| //if (graphController != null) { |
| // SVGDocument svgDoc = graphController.getSVGDocument(); |
| // svgCanvas.setDocument((SVGDocument) svgDoc.cloneNode(true)); |
| //} |
| return svgCanvas; |
| } |
| |
| public synchronized void addInput(final String inputName, final int inputDepth) { |
| addInput(inputName, inputDepth, null, null); |
| } |
| |
| public void addInput(final String inputName, final int inputDepth, String inputDescription, |
| String inputExample) { |
| /* |
| * Don't do anything if we already have the input registration panel for |
| * this input port |
| */ |
| RegistrationPanel inputRegistrationPanel = inputPanelMap.get(inputName); |
| if ((inputRegistrationPanel == null) || (inputRegistrationPanel.getDepth() != inputDepth)) { |
| inputRegistrationPanel = new RegistrationPanel(inputDepth, inputName, inputDescription, |
| inputExample, databaseConfiguration); |
| inputPanelMap.put(inputName, inputRegistrationPanel); |
| } else { |
| inputRegistrationPanel.setStatus( |
| "Drag to re-arrange, or drag files, URLs, or text to add", null); |
| inputRegistrationPanel.setDescription(inputDescription); |
| inputRegistrationPanel.setExample(inputExample); |
| } |
| tabsPane.addTab(inputName, inputRegistrationPanel); |
| tabsPane.revalidate(); |
| tabsPane.repaint(); |
| } |
| |
| public synchronized void removeInputTab(final String inputName) { |
| /* |
| * Only do something if we have a registration panel for this input port |
| * to begin with |
| */ |
| if (!inputPanelMap.containsKey(inputName)) |
| return; |
| RegistrationPanel inputRegistrationPanelToRemove = inputPanelMap |
| .remove(inputName); |
| tabsPane.remove(inputRegistrationPanelToRemove); |
| } |
| |
| private Bundle createInputDataBundle() throws IOException { |
| Bundle bundle = DataBundles.createBundle(); |
| Path inputs = DataBundles.getInputs(bundle); |
| for (String input : inputPanelMap.keySet()) { |
| RegistrationPanel registrationPanel = inputPanelMap.get(input); |
| Object userInput = registrationPanel.getUserInput(); |
| |
| Path port = DataBundles.getPort(inputs, input); |
| setValue(port, userInput); |
| } |
| return bundle; |
| } |
| |
| private void setValue(Path port, Object userInput) throws IOException { |
| if (userInput instanceof File) |
| DataBundles.setReference(port, ((File) userInput).toURI()); |
| else if (userInput instanceof URL) |
| try { |
| DataBundles.setReference(port, ((URL) userInput).toURI()); |
| } catch (URISyntaxException e) { |
| logger.warn(String.format("Error converting %1$s to URI", |
| userInput), e); |
| } |
| else if (userInput instanceof String) |
| DataBundles.setStringValue(port, (String) userInput); |
| else if (userInput instanceof List<?>) { |
| DataBundles.createList(port); |
| List<?> list = (List<?>) userInput; |
| for (Object object : list) |
| setValue(DataBundles.newListItem(port), object); |
| } else |
| logger.warn("Unknown input type : " |
| + userInput.getClass().getName()); |
| } |
| |
| /** |
| * Called when the run workflow action has been performed |
| * |
| * @param workflowInputs |
| * a map of named inputs in the form of T2Reference instances |
| */ |
| public abstract void handleLaunch(Bundle workflowInputs); |
| |
| public abstract void handleCancel(); |
| |
| private static void selectTopOfTextArea(DialogTextArea textArea) { |
| textArea.setSelectionStart(0); |
| textArea.setSelectionEnd(0); |
| } |
| |
| public void setWorkflowDescription(String workflowDescription) { |
| if (workflowDescription != null && !workflowDescription.isEmpty()) { |
| workflowDescriptionArea.setText(workflowDescription); |
| selectTopOfTextArea(workflowDescriptionArea); |
| } |
| } |
| |
| void setWorkflowAuthor(String workflowAuthor) { |
| if (workflowAuthor != null && !workflowAuthor.isEmpty()) { |
| workflowAuthorArea.setText(workflowAuthor); |
| selectTopOfTextArea(workflowAuthorArea); |
| } |
| } |
| |
| public String getWorkflowDescription() { |
| return workflowDescriptionArea.getText(); |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| createWorkflowGraphic.stopProcessing(); |
| super.finalize(); |
| } |
| |
| public class FileManagerObserver implements Observer<FileManagerEvent> { |
| @Override |
| public void notify(Observable<FileManagerEvent> sender, FileManagerEvent message) |
| throws Exception { |
| if (message instanceof ClosedDataflowEvent |
| && ((ClosedDataflowEvent) message).getDataflow() == workflow |
| .getParent()) { |
| // Remove listeners of various events |
| editManager.removeObserver(editManagerObserver); |
| fileManager.removeObserver(fileManagerObserver); |
| setVisible(false); |
| dispose(); // dispose off this window if the original workflow has been closed |
| } |
| } |
| } |
| |
| public class EditManagerObserver implements Observer<EditManagerEvent> { |
| @Override |
| public void notify(Observable<EditManagerEvent> sender, |
| final EditManagerEvent message) throws Exception { |
| if (message instanceof AbstractDataflowEditEvent |
| && ((AbstractDataflowEditEvent) message).getDataFlow() == workflow |
| .getParent()) { |
| workflowModified = true; |
| } |
| } |
| } |
| } |