You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2018/06/29 10:55:12 UTC

[25/27] incubator-taverna-plugin-component git commit: package rename folders

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/NestedWorkflowCreationDialog.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/NestedWorkflowCreationDialog.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/NestedWorkflowCreationDialog.java
new file mode 100644
index 0000000..3a467fd
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/NestedWorkflowCreationDialog.java
@@ -0,0 +1,666 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.BorderLayout.SOUTH;
+import static java.util.Collections.sort;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.ui.util.Utils.uniqueName;
+import static org.apache.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.AbstractAction;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.ListCellRenderer;
+
+//import net.sf.taverna.t2.lang.ui.DeselectingButton;
+//import net.sf.taverna.t2.workbench.edits.CompoundEdit;
+//import net.sf.taverna.t2.workbench.edits.Edit;
+//import net.sf.taverna.t2.workbench.edits.EditException;
+//import net.sf.taverna.t2.workbench.edits.EditManager;
+//import net.sf.taverna.t2.workbench.helper.HelpEnabledDialog;
+//import net.sf.taverna.t2.workbench.models.graph.GraphController;
+//import net.sf.taverna.t2.workbench.views.graph.GraphViewComponent;
+//import net.sf.taverna.t2.workflow.edits.AddActivityEdit;
+//import net.sf.taverna.t2.workflow.edits.AddActivityInputPortMappingEdit;
+//import net.sf.taverna.t2.workflow.edits.AddActivityOutputPortMappingEdit;
+//import net.sf.taverna.t2.workflow.edits.AddChildEdit;
+//import net.sf.taverna.t2.workflow.edits.AddDataLinkEdit;
+//import net.sf.taverna.t2.workflow.edits.AddProcessorInputPortEdit;
+//import net.sf.taverna.t2.workflow.edits.AddProcessorOutputPortEdit;
+//import net.sf.taverna.t2.workflow.edits.AddWorkflowInputPortEdit;
+//import net.sf.taverna.t2.workflow.edits.AddWorkflowOutputPortEdit;
+//import net.sf.taverna.t2.workflow.edits.RemoveChildEdit;
+//import net.sf.taverna.t2.workflow.edits.RemoveDataLinkEdit;
+//import net.sf.taverna.t2.workflow.edits.SetIterationStrategyStackEdit;
+
+import org.apache.log4j.Logger;
+import org.apache.taverna.annotation.annotationbeans.DescriptiveTitle;
+import org.apache.taverna.lang.ui.DeselectingButton;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.annotation.Annotation;
+import org.apache.taverna.scufl2.api.common.Named;
+import org.apache.taverna.scufl2.api.common.NamedSet;
+import org.apache.taverna.scufl2.api.common.Scufl2Tools;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.api.core.BlockingControlLink;
+import org.apache.taverna.scufl2.api.core.ControlLink;
+import org.apache.taverna.scufl2.api.core.DataLink;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.scufl2.api.core.Workflow;
+import org.apache.taverna.scufl2.api.port.InputActivityPort;
+import org.apache.taverna.scufl2.api.port.InputProcessorPort;
+import org.apache.taverna.scufl2.api.port.InputWorkflowPort;
+import org.apache.taverna.scufl2.api.port.OutputActivityPort;
+import org.apache.taverna.scufl2.api.port.OutputProcessorPort;
+import org.apache.taverna.scufl2.api.port.OutputWorkflowPort;
+import org.apache.taverna.scufl2.api.port.ProcessorPort;
+import org.apache.taverna.scufl2.api.port.ReceiverPort;
+import org.apache.taverna.scufl2.api.port.SenderPort;
+import org.apache.taverna.scufl2.api.profiles.Profile;
+import org.apache.taverna.workbench.edits.CompoundEdit;
+import org.apache.taverna.workbench.edits.Edit;
+import org.apache.taverna.workbench.edits.EditException;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.helper.HelpEnabledDialog;
+import org.apache.taverna.workbench.models.graph.GraphController;
+import org.apache.taverna.workbench.views.graph.GraphViewComponent;
+import org.apache.taverna.workflow.edits.AddActivityEdit;
+import org.apache.taverna.workflow.edits.AddActivityInputPortMappingEdit;
+import org.apache.taverna.workflow.edits.AddActivityOutputPortMappingEdit;
+import org.apache.taverna.workflow.edits.AddChildEdit;
+import org.apache.taverna.workflow.edits.AddDataLinkEdit;
+import org.apache.taverna.workflow.edits.AddProcessorInputPortEdit;
+import org.apache.taverna.workflow.edits.AddProcessorOutputPortEdit;
+import org.apache.taverna.workflow.edits.AddWorkflowInputPortEdit;
+import org.apache.taverna.workflow.edits.AddWorkflowOutputPortEdit;
+import org.apache.taverna.workflow.edits.RemoveChildEdit;
+import org.apache.taverna.workflow.edits.RemoveDataLinkEdit;
+import org.apache.taverna.workflow.edits.SetIterationStrategyStackEdit;
+import org.apache.taverna.workflowmodel.utils.AnnotationTools;
+
+/**
+ * @author alanrw
+ */
+public class NestedWorkflowCreationDialog extends HelpEnabledDialog {
+	private static final long serialVersionUID = 727059218457420449L;
+	private static final Logger logger = getLogger(NestedWorkflowCreationDialog.class);
+	private static final Comparator<Processor> processorComparator = new Comparator<Processor>() {
+		@Override
+		public int compare(Processor o1, Processor o2) {
+			return o1.getName().compareTo(o2.getName());
+		}
+	};
+	private static final ListCellRenderer<Object> defaultRenderer = new DefaultListCellRenderer();
+	private static final ListCellRenderer<Processor> processorRenderer = new ListCellRenderer<Processor>() {
+		@Override
+		public Component getListCellRendererComponent(
+				JList<? extends Processor> list,
+				Processor value, int index, boolean isSelected,
+				boolean cellHasFocus) {
+			return defaultRenderer.getListCellRendererComponent(list,
+					value.getName(), index, isSelected, cellHasFocus);
+		}
+	};
+
+	private final EditManager em;
+	private final GraphViewComponent graphView;
+	private final List<Processor> includedProcessors = new ArrayList<>();
+	private List<Processor> allProcessors;
+	private final List<Processor> includableProcessors = new ArrayList<>();
+
+	private JList<Processor> includableList = new JList<>();
+	private JList<Processor> includedList = new JList<>();
+	private final Workflow currentDataflow;
+	private JButton excludeButton;
+	private JButton includeButton;
+	private JButton okButton;
+	private JButton resetButton;
+	private JTextField nameField = new JTextField(30);
+
+	public NestedWorkflowCreationDialog(Frame owner, Object o,
+			Workflow dataflow, EditManager em, GraphViewComponent graphView) {
+		super(owner, "Nested workflow creation", true, null);
+		this.em = em;
+		this.graphView = graphView;
+
+		if (o instanceof Processor)
+			includedProcessors.add((Processor) o);
+		this.currentDataflow = dataflow;
+
+		allProcessors = new ArrayList<>(dataflow.getProcessors());
+
+		this.setLayout(new BorderLayout());
+		JPanel buttonPanel = new JPanel();
+		buttonPanel.setLayout(new FlowLayout());
+
+		okButton = new DeselectingButton(new OKAction(this));
+		buttonPanel.add(okButton);
+
+		resetButton = new DeselectingButton(new ResetAction(this));
+		buttonPanel.add(resetButton);
+
+		JButton cancelButton = new DeselectingButton(new CancelAction(this));
+		buttonPanel.add(cancelButton);
+
+		JPanel innerPanel = new JPanel(new BorderLayout());
+		JPanel processorChoice = createProcessorChoicePanel(dataflow);
+		innerPanel.add(processorChoice, CENTER);
+
+		JPanel namePanel = new JPanel(new FlowLayout());
+		namePanel.add(new JLabel("Workflow name: "));
+		nameField.setText("nested");
+		namePanel.add(nameField);
+		innerPanel.add(namePanel, SOUTH);
+
+		this.add(innerPanel, CENTER);
+
+		this.add(buttonPanel, SOUTH);
+		this.pack();
+		this.setSize(new Dimension(500, 800));
+	}
+
+	private JPanel createProcessorChoicePanel(Workflow dataflow) {
+		JPanel result = new JPanel();
+		result.setLayout(new GridLayout(0, 2));
+
+		JPanel includedProcessorsPanel = createIncludedProcessorsPanel();
+		JPanel includableProcessorsPanel = createIncludableProcessorsPanel();
+		result.add(includableProcessorsPanel);
+		result.add(includedProcessorsPanel);
+		updateLists();
+		return result;
+	}
+
+	private JPanel createIncludableProcessorsPanel() {
+		JPanel result = new JPanel();
+		result.setLayout(new BorderLayout());
+		result.add(new JLabel("Possible services"), NORTH);
+		includableList.setModel(new DefaultComboBoxModel<>(new Vector<>(
+				includableProcessors)));
+		includableList.setCellRenderer(processorRenderer);
+		result.add(new JScrollPane(includableList), CENTER);
+
+		includeButton = new DeselectingButton("Include", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				includedProcessors.addAll(includableList
+						.getSelectedValuesList());
+				calculateIncludableProcessors();
+				updateLists();
+			}
+		});
+
+		JPanel buttonPanel = new JPanel();
+		buttonPanel.setLayout(new FlowLayout());
+		buttonPanel.add(includeButton);
+		result.add(buttonPanel, SOUTH);
+		return result;
+	}
+
+	private void resetLists() {
+		includedProcessors.clear();
+		updateLists();
+	}
+
+	private JPanel createIncludedProcessorsPanel() {
+		JPanel result = new JPanel();
+		result.setLayout(new BorderLayout());
+		result.add(new JLabel("Included services"), NORTH);
+		includedList.setModel(new DefaultComboBoxModel<>(new Vector<>(
+				includedProcessors)));
+		includedList.setCellRenderer(processorRenderer);
+		result.add(new JScrollPane(includedList), CENTER);
+
+		excludeButton = new DeselectingButton("Exclude", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				includedProcessors.removeAll(includedList
+						.getSelectedValuesList());
+				calculateIncludableProcessors();
+				updateLists();
+			}
+		});
+		JPanel buttonPanel = new JPanel();
+		buttonPanel.setLayout(new FlowLayout());
+		buttonPanel.add(excludeButton);
+
+		result.add(buttonPanel, SOUTH);
+		return result;
+	}
+
+	private void updateLists() {
+		calculateIncludableProcessors();
+		sort(includedProcessors, processorComparator);
+		sort(includableProcessors, processorComparator);
+		includedList.setModel(new DefaultComboBoxModel<>(new Vector<>(
+				includedProcessors)));
+		includableList.setModel(new DefaultComboBoxModel<>(new Vector<>(
+				includableProcessors)));
+		boolean someIncludedProcessors = includedProcessors.size() > 0;
+		excludeButton.setEnabled(someIncludedProcessors);
+		okButton.setEnabled(someIncludedProcessors);
+		resetButton.setEnabled(someIncludedProcessors);
+		boolean someIncludableProcessors = includableProcessors.size() > 0;
+		includeButton.setEnabled(someIncludableProcessors);
+	}
+
+	public void calculateIncludableProcessors() {
+		includableProcessors.clear();
+		if (includedProcessors.isEmpty())
+			includableProcessors.addAll(allProcessors);
+		else
+			for (Processor p : includedProcessors) {
+				considerNearestUpstream(p);
+				considerNearestDownstream(p);
+			}
+		sort(includableProcessors, processorComparator);
+	}
+
+	private void considerNearestDownstream(Processor investigate) {
+		for (BlockingControlLink condition : investigate.controlLinksWaitingFor())
+			considerInclusion(condition.getBlock());
+
+		for (OutputProcessorPort outputPort : investigate.getOutputPorts())
+			for (DataLink datalink : outputPort.getDatalinksFrom()) {
+				ReceiverPort sink = datalink.getSendsTo();
+				if (sink instanceof InputProcessorPort)
+					considerInclusion(((InputProcessorPort) sink).getParent());
+			}
+	}
+
+	private void considerNearestUpstream(Processor investigate) {
+		for (BlockingControlLink condition : investigate.controlLinksBlocking())
+			considerInclusion(condition.getUntilFinished());
+		for (InputProcessorPort inputPort : investigate.getInputPorts())
+			for (DataLink incomingLink : inputPort.getDatalinksTo()) {
+				if (incomingLink == null)
+					continue;
+				SenderPort source = incomingLink.getReceivesFrom();
+				if (source instanceof OutputProcessorPort)
+					considerInclusion(((OutputProcessorPort) source).getParent());
+		}
+	}
+
+	private void considerInclusion(Processor p) {
+		if (!includedProcessors.contains(p)
+				&& !includableProcessors.contains(p))
+			includableProcessors.add(p);
+	}
+
+	private void createNestedWorkflow() {
+		final List<Edit<?>> currentWorkflowEditList = new ArrayList<>();
+		Map<Object, Object> oldNewMapping = new HashMap<>();
+		Map<DataLink, String> linkProcessorPortMapping = new HashMap<>();
+		Map<SenderPort, OutputWorkflowPort> outputPortMap = new HashMap<>();
+		Map<ReceiverPort, InputWorkflowPort> inputPortMap = new HashMap<>();
+
+		Profile profile;//FIXME
+		Processor nestingProcessor = createNestingProcessor(currentWorkflowEditList);
+		Workflow nestedDataflow = createNestedDataflow();
+
+		transferProcessors(currentWorkflowEditList, oldNewMapping,
+				nestedDataflow);
+		transferDatalinks(oldNewMapping, linkProcessorPortMapping,
+				outputPortMap, inputPortMap, nestedDataflow);
+		transferConditions(currentWorkflowEditList, oldNewMapping,
+				nestingProcessor);
+		addDataflowToNestingProcessor(nestingProcessor, nestedDataflow, profile);
+		currentWorkflowEditList.add(new AddChildEdit<>(currentDataflow,
+				nestingProcessor));
+		createDatalinkEdits(currentWorkflowEditList, oldNewMapping,
+				linkProcessorPortMapping, nestingProcessor);
+
+		try {
+			GraphController gc = graphView.getGraphController(currentDataflow);
+			gc.setExpandNestedDataflow(nestingProcessor.getActivity(profile), true);
+			em.doDataflowEdit(currentDataflow.getParent(), new CompoundEdit(
+					currentWorkflowEditList));
+			gc.redraw();
+		} catch (EditException e1) {
+			logger.error("failed to manufacture nested workflow", e1);
+		}
+	}
+
+	private void addDataflowToNestingProcessor(Processor nestingProcessor,
+			Workflow nestedDataflow, Profile profile) {
+		Activity da = new Activity();
+		da.setParent(profile);
+		da.createConfiguration(NESTED_WORKFLOW).getJsonAsObjectNode()
+				.put("nestedWorkflow", nestedDataflow.getName());
+		try {
+			new AddActivityEdit(nestingProcessor, da).doEdit();
+			new SetIterationStrategyStackEdit(nestingProcessor, null/*FIXME*/).doEdit();
+			for (InputActivityPort aip : da.getInputPorts()) {
+				InputProcessorPort pip = new InputProcessorPort();
+				pip.setName(aip.getName());
+				pip.setDepth(aip.getDepth());
+				new AddProcessorInputPortEdit(nestingProcessor, pip).doEdit();
+				new AddActivityInputPortMappingEdit(da, pip, aip).doEdit();
+			}
+			for (OutputActivityPort aop : da.getOutputPorts()) {
+				OutputProcessorPort pop = new OutputProcessorPort();
+				pop.setName(aop.getName());
+				pop.setDepth(aop.getDepth());
+				pop.setGranularDepth(aop.getGranularDepth());
+				new AddProcessorOutputPortEdit(nestingProcessor, pop).doEdit();
+				new AddActivityOutputPortMappingEdit(da, pop, aop).doEdit();
+			}
+		} catch (EditException e1) {
+			logger.error("failed to add ports to processor", e1);
+		}
+	}
+
+	private void createDatalinkEdits(List<Edit<?>> editList,
+			Map<Object, Object> oldNewMapping,
+			Map<DataLink, String> linkProcessorPortMapping,
+			Processor nestingProcessor) {
+		for (DataLink dl : currentDataflow.getDataLinks())
+			if (oldNewMapping.containsKey(dl.getReceivesFrom())
+					&& oldNewMapping.containsKey(dl.getSendsTo()))
+				// Internal to nested workflow
+				editList.add(new RemoveDataLinkEdit(dl.getParent(), dl));
+			else if (oldNewMapping.containsKey(dl.getReceivesFrom())) {
+				// Coming out of nested workflow
+				OutputProcessorPort nestedPort = nestingProcessor
+						.getOutputPorts().getByName(
+								linkProcessorPortMapping.get(dl));
+				if (nestedPort != null) {
+					DataLink replacementDatalink = new DataLink(nestedPort
+							.getParent().getParent(), nestedPort,
+							dl.getSendsTo());
+					editList.add(new RemoveDataLinkEdit(dl.getParent(), dl));
+					editList.add(new AddDataLinkEdit(nestedPort.getParent()
+							.getParent(), replacementDatalink));
+				}
+			} else if (oldNewMapping.containsKey(dl.getSendsTo())) {
+				// Coming into nested workflow
+				InputProcessorPort nestedPort = nestingProcessor
+						.getInputPorts().getByName(
+								linkProcessorPortMapping.get(dl));
+				if (nestedPort != null) {
+					DataLink replacementDatalink = new DataLink(nestedPort
+							.getParent().getParent(), dl.getReceivesFrom(),
+							nestedPort);
+					editList.add(new RemoveDataLinkEdit(dl.getParent(), dl));
+					editList.add(new AddDataLinkEdit(nestedPort.getParent()
+							.getParent(), replacementDatalink));
+				}
+			}
+	}
+
+	private void transferConditions(List<Edit<?>> editList,
+			Map<Object, Object> oldNewMapping, Processor nestingProcessor) {
+		for (Processor p : currentDataflow.getProcessors()) {
+			boolean isTargetMoved = oldNewMapping.containsKey(p);
+			for (BlockingControlLink c : p.controlLinksWaitingFor()) {
+				Processor pre = c.getUntilFinished();
+				boolean isControlMoved = oldNewMapping.containsKey(pre);
+				if (isTargetMoved && isControlMoved) {
+					// Add in new condition
+					new BlockingControlLink(
+							(Processor) oldNewMapping.get(pre),
+							(Processor) oldNewMapping.get(p));
+				} else if (isTargetMoved) {
+					editList.add(new RemoveChildEdit<>(c.getParent(),c));
+					editList.add(new AddChildEdit<>(c.getParent(),
+							new BlockingControlLink(pre, nestingProcessor)));
+				} else if (isControlMoved) {
+					editList.add(new RemoveChildEdit<>(c.getParent(), c));
+					editList.add(new AddChildEdit<>(c.getParent(),
+							new BlockingControlLink(nestingProcessor, p)));
+				}
+			}
+		}
+	}
+
+	private void transferDatalinks(Map<Object, Object> oldNewMapping,
+			Map<DataLink, String> linkProcessorPortMapping,
+			Map<SenderPort, OutputWorkflowPort> outputPortMap,
+			Map<ReceiverPort, InputWorkflowPort> inputPortMap,
+			Workflow nestedDataflow) {
+		NamedSet<InputWorkflowPort> inputPorts = new NamedSet<>();
+		NamedSet<OutputWorkflowPort> outputPorts = new NamedSet<>();
+
+		for (DataLink dl : currentDataflow.getDataLinks()) {
+			final SenderPort datalinkSource = dl.getReceivesFrom();
+			final ReceiverPort datalinkSink = dl.getSendsTo();
+			if (oldNewMapping.containsKey(datalinkSource)
+					&& oldNewMapping.containsKey(datalinkSink)) {
+				// Internal to nested workflow
+				DataLink newDatalink = new DataLink(null,
+						(SenderPort) oldNewMapping.get(datalinkSource),
+						(ReceiverPort) oldNewMapping.get(datalinkSink));
+				try {
+					new AddDataLinkEdit(nestedDataflow, newDatalink).doEdit();
+				} catch (EditException e1) {
+					logger.error("failed to connect datalink", e1);
+				}
+			} else if (oldNewMapping.containsKey(datalinkSource)) {
+				OutputWorkflowPort dop = null;
+				if (!outputPortMap.containsKey(datalinkSource)) {
+					dop = new OutputWorkflowPort(nestedDataflow, uniqueName(
+							datalinkSource.getName(), outputPorts));
+					outputPorts.add(dop);
+					outputPortMap.put(datalinkSource, dop);
+				} else
+					dop = outputPortMap.get(datalinkSource);
+				String portName = dop.getName();
+				// Coming out of nested workflow
+				linkProcessorPortMapping.put(dl, portName);
+				try {
+					new AddWorkflowOutputPortEdit(nestedDataflow, dop).doEdit();
+					DataLink newDatalink = new DataLink(
+							(SenderPort) oldNewMapping.get(datalinkSource),
+							dop.getInternalInputPort());
+					new AddDataLinkEdit(nestedDataflow, newDatalink).doEdit();
+				} catch (EditException e1) {
+					logger.error("failed to add dataflow output", e1);
+				}
+			} else if (oldNewMapping.containsKey(datalinkSink)) {
+				InputWorkflowPort dip = null;
+				if (!inputPortMap.containsKey(datalinkSink)) {
+					dip = new InputWorkflowPort(nestedDataflow, uniqueName(
+							datalinkSink.getName(), inputPorts));
+					inputPorts.add(dip);
+					dip.setDepth(dl.getResolvedDepth());
+					inputPortMap.put(datalinkSink, dip);
+				} else
+					dip = inputPortMap.get(datalinkSink);
+				String portName = dip.getName();
+				// Coming into nested workflow
+				linkProcessorPortMapping.put(dl, portName);
+				try {
+					new AddWorkflowInputPortEdit(nestedDataflow, dip).doEdit();
+					DataLink newDatalink = new DataLink(
+							dip.getInternalOutputPort(),
+							(ReceiverPort) oldNewMapping.get(datalinkSink));
+					new AddDataLinkEdit(nestedDataflow, newDatalink).doEdit();
+				} catch (EditException e1) {
+					logger.error("failed to add dataflow input", e1);
+				}
+			}
+		}
+	}
+
+	private void transferProcessors(List<Edit<?>> editList,
+			Map<Object, Object> oldNewMapping, Workflow nestedDataflow) {
+		for (Processor entity : includedProcessors)
+			try {
+				if (entity instanceof Processor)
+					transferProcessor(editList, oldNewMapping, nestedDataflow,
+							(Processor) entity);
+				/*else if (entity instanceof Merge)
+					//FIXME what to do here? Anything?
+					transferMerge(editList, oldNewMapping, nestedDataflow,
+							(Merge) entity);*/
+			} catch (Exception e1) {
+				logger.error("failed to transfer processor", e1);
+			}
+	}
+
+	/*private void transferMerge(List<Edit<?>> editList,
+			Map<Object, Object> oldNewMapping, Workflow nestedDataflow,
+			Merge merge) throws EditException {
+		editList.add(edits.getRemoveMergeEdit(currentDataflow, merge));
+		Merge newMerge = edits.createMerge(nestedDataflow);
+		edits.getAddMergeEdit(nestedDataflow, newMerge).doEdit();
+		oldNewMapping.put(merge, newMerge);
+		for (MergeInputPort mip : merge.getInputPorts()) {
+			MergeInputPort newMip = edits.createMergeInputPort(newMerge,
+					mip.getName(), mip.getDepth());
+			edits.getAddMergeInputPortEdit(newMerge, newMip).doEdit();
+			oldNewMapping.put(mip, newMip);
+		}
+		oldNewMapping.put(merge.getOutputPort(), newMerge.getOutputPort());
+	}*/
+
+	private void transferProcessor(List<Edit<?>> editList,
+			Map<Object, Object> oldNewMapping, Workflow nestedDataflow,
+			Processor p) throws Exception {
+		editList.add(new RemoveChildEdit<>(currentDataflow, p));
+		Processor newProcessor = (Processor) p.clone();
+		newProcessor.setParent(nestedDataflow);
+		oldNewMapping.put(p, newProcessor);
+		for (InputProcessorPort pip : p.getInputPorts())
+			for (InputProcessorPort newPip : newProcessor.getInputPorts())
+				if (pip.getName().equals(newPip.getName())) {
+					oldNewMapping.put(pip, newPip);
+					break;
+				}
+		for (OutputProcessorPort pop : p.getOutputPorts())
+			for (OutputProcessorPort newPop : newProcessor.getOutputPorts())
+				if (pop.getName().equals(newPop.getName())) {
+					oldNewMapping.put(pop, newPop);
+					break;
+				}
+	}
+
+	private Processor createNestingProcessor(List<Edit<?>> editList) {
+		//TODO check what workflow the new processor is going into
+		Processor nestingProcessor = new Processor(currentDataflow, uniqueName(
+				nameField.getText(), currentDataflow.getProcessors()));
+		if (includedProcessors.size() != 1)
+			return nestingProcessor;
+		Processor includedProcessor = includedProcessors.get(0);
+		for (Annotation a: includedProcessor.getAnnotations()) {
+			Annotation newAnn = (Annotation) a.clone();
+			newAnn.setTarget(nestingProcessor);
+			editList.add(new AddChildEdit<>(a.getParent(), newAnn));
+		}
+		return nestingProcessor;
+	}
+
+	private Workflow createNestedDataflow() {
+		Workflow nestedDataflow = new Workflow(uniqueName(nameField.getText(),
+				currentDataflow.getParent().getWorkflows()));
+		// Set the title of the nested workflow to the name suggested by the user
+		try {
+			new AnnotationTools().setAnnotationString(nestedDataflow,
+					DescriptiveTitle.class, nameField.getText()).doEdit();
+		} catch (EditException ex) {
+			logger.error("failed to put annotation on nested dataflow", ex);
+		}
+		return nestedDataflow;
+	}
+
+	private final class OKAction extends AbstractAction {
+		private static final long serialVersionUID = 6516891432445682857L;
+		private final JDialog dialog;
+
+		private OKAction(JDialog dialog) {
+			super("OK");
+			this.dialog = dialog;
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			if (includedProcessors.isEmpty()) {
+				showMessageDialog(
+						null,
+						"At least one service must be included in the nested workflow",
+						"Nested workflow creation", WARNING_MESSAGE);
+				return;
+			}
+
+			createNestedWorkflow();
+			dialog.setVisible(false);
+		}
+	}
+
+	private final class ResetAction extends AbstractAction {
+		private static final long serialVersionUID = 7296742769289881218L;
+
+		private ResetAction(JDialog dialog) {
+			super("Reset");
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			resetLists();
+		}
+	}
+
+	private final class CancelAction extends AbstractAction {
+		private static final long serialVersionUID = -7842176979437027091L;
+		private final JDialog dialog;
+
+		private CancelAction(JDialog dialog) {
+			super("Cancel");
+			this.dialog = dialog;
+		}
+
+		@Override
+		public void actionPerformed(ActionEvent e) {
+			dialog.setVisible(false);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/NestedWorkflowCreatorMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/NestedWorkflowCreatorMenuAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/NestedWorkflowCreatorMenuAction.java
new file mode 100644
index 0000000..0cdb2ba
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/NestedWorkflowCreatorMenuAction.java
@@ -0,0 +1,94 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu;
+
+import java.awt.Dialog;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import org.apache.taverna.activities.dataflow.servicedescriptions.DataflowActivityIcon;
+
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.scufl2.api.core.Workflow;
+import org.apache.taverna.ui.menu.AbstractContextualMenuAction;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.selection.SelectionManager;
+import org.apache.taverna.workbench.views.graph.GraphViewComponent;
+
+/**
+ * @author alanrw
+ */
+public class NestedWorkflowCreatorMenuAction extends
+		AbstractContextualMenuAction {
+	private static final URI configureSection = URI
+			.create("http://taverna.sf.net/2009/contextMenu/configure");
+
+	private SelectionManager sm;
+	private EditManager em;
+	private GraphViewComponent gv;
+
+	public NestedWorkflowCreatorMenuAction() {
+		super(configureSection, 70);
+	}
+
+	public void setEditManager(EditManager editManager) {
+		em = editManager;
+	}
+	public void setGraphView(GraphViewComponent graphView) {
+		gv = graphView;
+	}
+	public void setSelectionManager(SelectionManager selectionManager) {
+		sm = selectionManager;
+	}
+
+	@Override
+	public boolean isEnabled() {
+		Object selection = getContextualSelection().getSelection();
+		if (!super.isEnabled() || selection == null)
+			return false;
+		if (selection instanceof Processor)
+			return true;
+		if (!(selection instanceof Workflow))
+			return false;
+		return !((Workflow) selection).getProcessors().isEmpty();
+	}
+
+	@Override
+	protected Action createAction() {
+		return new AbstractAction("Create nested workflow...",
+				DataflowActivityIcon.getDataflowIcon()) {
+			private static final long serialVersionUID = -3121307982540205215L;
+
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				createNestedWorkflow();
+			}
+		};
+	}
+
+	private void createNestedWorkflow() {
+		Dialog dialog = new NestedWorkflowCreationDialog(null,
+				getContextualSelection().getSelection(),
+				sm.getSelectedWorkflow(), em, gv);
+		dialog.setVisible(true);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/OpenComponentFromComponentActivityAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/OpenComponentFromComponentActivityAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/OpenComponentFromComponentActivityAction.java
new file mode 100644
index 0000000..2bce63a
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/OpenComponentFromComponentActivityAction.java
@@ -0,0 +1,81 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu;
+
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.event.ActionEvent;
+import java.net.MalformedURLException;
+
+import org.apache.log4j.Logger;
+import io.github.taverna_extras.component.api.ComponentFactory;
+import io.github.taverna_extras.component.api.Version;
+import io.github.taverna_extras.component.ui.ComponentAction;
+import io.github.taverna_extras.component.ui.ComponentActivityConfigurationBean;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.exceptions.OpenException;
+import org.apache.taverna.workbench.views.graph.GraphViewComponent;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+public class OpenComponentFromComponentActivityAction extends ComponentAction {
+	private static Logger logger = getLogger(OpenComponentFromComponentActivityAction.class);
+
+	private final FileManager fileManager;
+	private final ComponentFactory factory;
+	private final FileType fileType;
+
+	public OpenComponentFromComponentActivityAction(FileManager fileManager,
+			ComponentFactory factory, FileType ft,
+			GraphViewComponent graphView, ComponentServiceIcon icon) {
+		super("Open component...", graphView);
+		this.fileManager = fileManager;
+		this.factory = factory;
+		this.fileType = ft;
+		setIcon(icon);
+	}
+
+	private Activity selection;
+
+	@Override
+	public void actionPerformed(ActionEvent ev) {
+		try {
+			Version.ID ident = new ComponentActivityConfigurationBean(
+					selection.getConfiguration(), factory);
+			WorkflowBundle d = fileManager.openDataflow(fileType, ident);
+			markGraphAsBelongingToComponent(d);
+		} catch (OpenException e) {
+			logger.error("failed to open component", e);
+		} catch (MalformedURLException e) {
+			logger.error("bad URL in component description", e);
+		}
+	}
+
+	public void setSelection(Activity selection) {
+		this.selection = selection;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/OpenComponentFromComponentActivityMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/OpenComponentFromComponentActivityMenuAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/OpenComponentFromComponentActivityMenuAction.java
new file mode 100644
index 0000000..6f28816
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/OpenComponentFromComponentActivityMenuAction.java
@@ -0,0 +1,104 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import io.github.taverna_extras.component.api.ComponentFactory;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.ui.menu.AbstractContextualMenuAction;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.selection.SelectionManager;
+import org.apache.taverna.workbench.views.graph.GraphViewComponent;
+
+/**
+ * @author alanrw
+ */
+public class OpenComponentFromComponentActivityMenuAction extends
+		AbstractContextualMenuAction {
+	private static final URI configureSection = URI
+			.create("http://taverna.sf.net/2009/contextMenu/configure");
+
+	private SelectionManager sm;
+	private FileManager fileManager;
+	private ComponentFactory factory;
+	private FileType fileType;
+	private GraphViewComponent graphView;
+	private ComponentServiceIcon icon;
+
+	public OpenComponentFromComponentActivityMenuAction() {
+		super(configureSection, 75);
+	}
+
+	public void setSelectionManager(SelectionManager sm) {
+		this.sm = sm;
+	}
+
+	public void setFileManager(FileManager fileManager) {
+		this.fileManager = fileManager;
+	}
+
+	public void setComponentFactory(ComponentFactory factory) {
+		this.factory = factory;
+	}
+
+	public void setFileType(FileType fileType) {
+		this.fileType = fileType;
+	}
+
+	public void setGraphView(GraphViewComponent graphView) {
+		this.graphView = graphView;
+	}
+
+	public void setIcon(ComponentServiceIcon icon) {
+		this.icon = icon;
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return getSelectedActivity() != null;
+	}
+
+	@Override
+	protected Action createAction() {
+		OpenComponentFromComponentActivityAction action = new OpenComponentFromComponentActivityAction(
+				fileManager, factory, fileType, graphView, icon);
+		action.setSelection(getSelectedActivity());
+		return action;
+	}
+
+	private Activity getSelectedActivity() {
+		Object selection = getContextualSelection().getSelection();
+		if (!super.isEnabled() || !(selection instanceof Processor))
+			return null;
+
+		try {
+			return ((Processor) selection).getActivity(sm.getSelectedProfile());
+		} catch (RuntimeException e) {
+			return null;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ReplaceByComponentAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ReplaceByComponentAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ReplaceByComponentAction.java
new file mode 100644
index 0000000..ebb51a8
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ReplaceByComponentAction.java
@@ -0,0 +1,275 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.SOUTH;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.OK_CANCEL_OPTION;
+import static javax.swing.JOptionPane.OK_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static io.github.taverna_extras.component.api.config.ComponentPropertyNames.COMPONENT_NAME;
+import static io.github.taverna_extras.component.ui.ComponentActivityConfigurationBean.ignorableNames;
+import static io.github.taverna_extras.component.ui.util.Utils.uniqueName;
+import static org.apache.taverna.scufl2.api.common.Scufl2Tools.NESTED_WORKFLOW;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+
+import io.github.taverna_extras.component.api.Component;
+import io.github.taverna_extras.component.api.ComponentFactory;
+import io.github.taverna_extras.component.api.Family;
+import io.github.taverna_extras.component.api.Registry;
+import io.github.taverna_extras.component.api.Version;
+import io.github.taverna_extras.component.ui.ComponentActivityConfigurationBean;
+import io.github.taverna_extras.component.ui.panel.ComponentChooserPanel;
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+import io.github.taverna_extras.component.ui.util.Utils;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.common.Scufl2Tools;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.scufl2.api.core.Workflow;
+import org.apache.taverna.scufl2.api.port.InputActivityPort;
+import org.apache.taverna.scufl2.api.port.InputProcessorPort;
+import org.apache.taverna.scufl2.api.port.OutputActivityPort;
+import org.apache.taverna.scufl2.api.port.OutputProcessorPort;
+import org.apache.taverna.workbench.edits.CompoundEdit;
+import org.apache.taverna.workbench.edits.Edit;
+import org.apache.taverna.workbench.edits.EditException;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.selection.SelectionManager;
+import org.apache.taverna.workflow.edits.AddActivityEdit;
+import org.apache.taverna.workflow.edits.AddActivityInputPortMappingEdit;
+import org.apache.taverna.workflow.edits.AddActivityOutputPortMappingEdit;
+import org.apache.taverna.workflow.edits.RemoveActivityEdit;
+import org.apache.taverna.workflow.edits.RenameEdit;
+
+/**
+ * @author alanrw
+ */
+public class ReplaceByComponentAction extends AbstractAction {
+	private static final long serialVersionUID = 7364648399658711574L;
+
+	private final EditManager em;
+	private final ComponentPreference prefs;
+	private final SelectionManager sm;
+	private final ComponentFactory factory;
+	private final Scufl2Tools tools = new Scufl2Tools();
+
+	private Processor selection;
+
+	public ReplaceByComponentAction(ComponentPreference prefs,
+			ComponentFactory factory, EditManager em, SelectionManager sm,
+			ComponentServiceIcon icon) {
+		super("Replace by component...", icon.getIcon());
+		this.prefs = prefs;
+		this.em = em;
+		this.sm = sm;
+		this.factory = factory;
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		JPanel overallPanel = new JPanel(new BorderLayout());
+		ComponentChooserPanel panel = new ComponentChooserPanel(prefs);
+		overallPanel.add(panel, CENTER);
+		JPanel checkBoxPanel = new JPanel(new FlowLayout());
+		JCheckBox replaceAllCheckBox = new JCheckBox(
+				"Replace all matching services");
+		checkBoxPanel.add(replaceAllCheckBox);
+		checkBoxPanel.add(new JSeparator());
+		JCheckBox renameServicesCheckBox = new JCheckBox("Rename service(s)");
+		checkBoxPanel.add(renameServicesCheckBox);
+		renameServicesCheckBox.setSelected(true);
+		overallPanel.add(checkBoxPanel, SOUTH);
+		int answer = showConfirmDialog(null, overallPanel, "Component choice",
+				OK_CANCEL_OPTION);
+		if (answer == OK_OPTION)
+			doReplace(panel.getChosenRegistry(), panel.getChosenFamily(),
+					replaceAllCheckBox.isSelected(),
+					renameServicesCheckBox.isSelected(),
+					panel.getChosenComponent());
+	}
+
+	private void doReplace(Registry chosenRegistry, Family chosenFamily,
+			boolean replaceAll, boolean rename, Component chosenComponent) {
+		Version chosenVersion = chosenComponent.getComponentVersionMap().get(
+				chosenComponent.getComponentVersionMap().lastKey());
+		Version.ID ident = new Version.Identifier(
+				chosenRegistry.getRegistryBase(), chosenFamily.getName(),
+				chosenComponent.getName(), chosenVersion.getVersionNumber());
+
+		ComponentActivityConfigurationBean cacb = new ComponentActivityConfigurationBean(
+				ident, factory);
+
+		try {
+			if (replaceAll) {
+				Activity baseActivity = selection.getActivity(sm
+						.getSelectedProfile());
+				URI activityType = baseActivity.getType();
+				String configString = getConfigString(baseActivity);
+
+				replaceAllMatchingActivities(activityType, cacb, configString,
+						rename, sm.getSelectedWorkflow());
+			} else
+				replaceActivity(cacb, selection, rename,
+						sm.getSelectedWorkflow());
+		} catch (Exception e) {
+			showMessageDialog(
+					null,
+					"Failed to replace nested workflow with component: "
+							+ e.getMessage(), "Component Problem",
+					ERROR_MESSAGE);
+		}
+	}
+
+	private String getConfigString(Activity baseActivity) {
+		return baseActivity.getConfiguration().getJsonAsString();
+	}
+
+	private void replaceAllMatchingActivities(URI activityType,
+			ComponentActivityConfigurationBean cacb, String configString,
+			boolean rename, Workflow d) throws IntermediateException {
+		for (Processor p : d.getProcessors()) {
+			Activity a = p.getActivity(sm.getSelectedProfile());
+			if (a.getType().equals(activityType)
+					&& getConfigString(a).equals(configString))
+				replaceActivity(cacb, p, rename, d);
+			else if (a.getType().equals(NESTED_WORKFLOW))
+				replaceAllMatchingActivities(activityType, cacb, configString,
+						rename,
+						tools.nestedWorkflowForProcessor(p, a.getParent()));
+		}
+	}
+
+	private void replaceActivity(ComponentActivityConfigurationBean cacb,
+			Processor p, boolean rename, Workflow d) throws IntermediateException {
+		final Activity originalActivity = p.getActivity(sm.getSelectedProfile());
+		final List<Edit<?>> currentWorkflowEditList = new ArrayList<>();
+				
+		Activity replacementActivity = new Activity();
+		try {
+			URI configType;
+			replacementActivity.createConfiguration(configType);
+			
+			replacementActivity.configure(cacb);
+			//FIXME
+		} catch (Exception e) {
+			throw new IntermediateException(
+					"Unable to configure component", e);
+		}
+		if (originalActivity.getInputPorts().size() != replacementActivity
+				.getInputPorts().size())
+			throw new IntermediateException(
+					"Component does not have matching ports", null);
+
+		int replacementOutputSize = replacementActivity.getOutputPorts().size();
+		int originalOutputSize = originalActivity.getOutputPorts().size();
+		for (String name : ignorableNames) {
+			if (originalActivity.getOutputPorts().getByName(name) != null)
+				originalOutputSize--;
+			if (replacementActivity.getOutputPorts().getByName(name) != null)
+				replacementOutputSize--;
+		}
+
+		int sizeDifference = replacementOutputSize - originalOutputSize;
+		if (sizeDifference != 0)
+			throw new IntermediateException(
+					"Component does not have matching ports", null);
+
+		for (InputActivityPort aip : originalActivity.getInputPorts()) {
+			String aipName = aip.getName();
+			int aipDepth = aip.getDepth();
+			InputActivityPort caip = replacementActivity.getInputPorts().getByName(aipName);
+			if ((caip == null) || (caip.getDepth() != aipDepth))
+				throw new RuntimeException("Original input port "
+						+ aipName + " is not matched");
+		}
+		for (OutputActivityPort aop : originalActivity.getOutputPorts()) {
+			String aopName = aop.getName();
+			int aopDepth = aop.getDepth();
+			OutputActivityPort caop = replacementActivity.getOutputPorts().getByName(aopName);
+			if ((caop == null || aopDepth != caop.getDepth())
+					&& !ignorableNames.contains(aopName))
+				throw new IntermediateException(
+						"Original output port " + aopName + " is not matched", null);
+		}
+
+		for (InputProcessorPort pip : p.getInputPorts()) {
+			InputActivityPort iap = replacementActivity.getInputPorts()
+					.getByName(pip.getName());
+			if (iap == null)
+				iap = new InputActivityPort(replacementActivity, pip.getName());
+			currentWorkflowEditList.add(new AddActivityInputPortMappingEdit(
+					replacementActivity, pip, iap));
+		}
+
+		for (OutputProcessorPort pop : p.getOutputPorts()) {
+			OutputActivityPort oap = replacementActivity.getOutputPorts()
+					.getByName(pop.getName());
+			if (oap == null)
+				oap = new OutputActivityPort(replacementActivity, pop.getName());
+			currentWorkflowEditList.add(new AddActivityOutputPortMappingEdit(
+					replacementActivity, pop, oap));
+		}
+
+		currentWorkflowEditList
+				.add(new AddActivityEdit(p, replacementActivity));
+		currentWorkflowEditList
+				.add(new RemoveActivityEdit(p, originalActivity));
+		
+		if (rename) {
+			String possibleName = replacementActivity.getConfiguration()
+					.getJsonAsObjectNode().get(COMPONENT_NAME).textValue();
+			currentWorkflowEditList.add(new RenameEdit<>(p, uniqueName(
+					possibleName, d.getProcessors())));
+		}
+		try {
+			em.doDataflowEdit(d.getParent(), new CompoundEdit(
+					currentWorkflowEditList));
+		} catch (EditException e) {
+			throw new IntermediateException(
+					"Unable to replace with component", e);
+		}
+	}
+
+	public void setSelection(Processor selection) {
+		this.selection = selection;
+	}
+
+	@SuppressWarnings("serial")
+	private static class IntermediateException extends Exception {
+		IntermediateException(String msg, Throwable cause) {
+			super(msg, cause);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ReplaceByComponentMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ReplaceByComponentMenuAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ReplaceByComponentMenuAction.java
new file mode 100644
index 0000000..470ad40
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ReplaceByComponentMenuAction.java
@@ -0,0 +1,87 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import io.github.taverna_extras.component.api.ComponentFactory;
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.ui.menu.AbstractContextualMenuAction;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.selection.SelectionManager;
+
+/**
+ * @author alanrw
+ */
+public class ReplaceByComponentMenuAction extends AbstractContextualMenuAction {
+	private static final URI configureSection = URI
+			.create("http://taverna.sf.net/2009/contextMenu/configure");
+
+	private ComponentPreference preferences;
+	private EditManager editManager;
+	private SelectionManager selectionManager;
+	private ComponentFactory factory;
+	private ComponentServiceIcon icon;
+
+	public ReplaceByComponentMenuAction() {
+		super(configureSection, 75);
+	}
+
+	public void setPreferences(ComponentPreference preferences) {
+		this.preferences = preferences;
+	}
+
+	public void setEditManager(EditManager editManager) {
+		this.editManager = editManager;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+	}
+
+	public void setComponentFactory(ComponentFactory factory) {
+		this.factory = factory;
+	}
+
+	public void setIcon(ComponentServiceIcon icon) {
+		this.icon = icon;
+	}
+
+	@Override
+	public boolean isEnabled() {
+		Object selection = getContextualSelection().getSelection();
+		if (!super.isEnabled())
+			return false;
+		return (selection instanceof Processor);
+	}
+
+	@Override
+	protected Action createAction() {
+		ReplaceByComponentAction action = new ReplaceByComponentAction(
+				preferences, factory, editManager, selectionManager, icon);
+		action.setSelection((Processor) getContextualSelection().getSelection());
+		return action;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/AbstractComponentMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/AbstractComponentMenuAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/AbstractComponentMenuAction.java
new file mode 100644
index 0000000..671e190
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/AbstractComponentMenuAction.java
@@ -0,0 +1,31 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu.component;
+
+import static io.github.taverna_extras.component.ui.menu.component.ComponentMenuSection.COMPONENT_SECTION;
+
+import java.net.URI;
+import org.apache.taverna.ui.menu.AbstractMenuAction;
+
+abstract class AbstractComponentMenuAction extends AbstractMenuAction {
+	public AbstractComponentMenuAction(int positionHint, URI id) {
+		super(COMPONENT_SECTION, positionHint, id);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCloseAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCloseAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCloseAction.java
new file mode 100644
index 0000000..da22a60
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCloseAction.java
@@ -0,0 +1,73 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu.component;
+
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+import org.apache.log4j.Logger;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+import io.github.taverna_extras.component.ui.util.Utils;
+import org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.events.FileManagerEvent;
+
+/**
+ * @author alanrw
+ */
+public class ComponentCloseAction extends AbstractAction implements
+		Observer<FileManagerEvent> {
+	private static final long serialVersionUID = -153986599735293879L;
+	private static final String CLOSE_COMPONENT = "Close component";
+	@SuppressWarnings("unused")
+	private static Logger logger = getLogger(ComponentCloseAction.class);
+
+	private Action closeAction;
+	private final Utils utils;
+
+	public ComponentCloseAction(Action closeWorkflowAction, FileManager fm,
+			ComponentServiceIcon icon, Utils utils) {
+		super(CLOSE_COMPONENT, icon.getIcon());
+		closeAction = closeWorkflowAction;
+		this.utils = utils;
+		fm.addObserver(this);
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent arg0) {
+		closeAction.actionPerformed(arg0);
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return utils.currentDataflowIsComponent();
+	}
+
+	@Override
+	public void notify(Observable<FileManagerEvent> sender,
+			FileManagerEvent message) throws Exception {
+		setEnabled(utils.currentDataflowIsComponent());
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCloseMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCloseMenuAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCloseMenuAction.java
new file mode 100644
index 0000000..6c40a5f
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCloseMenuAction.java
@@ -0,0 +1,66 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu.component;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+import io.github.taverna_extras.component.ui.util.Utils;
+import org.apache.taverna.workbench.file.FileManager;
+
+/**
+ * @author alanrw
+ */
+public class ComponentCloseMenuAction extends AbstractComponentMenuAction {
+	private static final URI CLOSE_COMPONENT_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#componentClose");
+
+	private Action action;
+	private FileManager fm;
+	private ComponentServiceIcon icon;
+	private Utils utils;
+
+	public ComponentCloseMenuAction() {
+		super(1000, CLOSE_COMPONENT_URI);
+	}
+	
+	public void setCloseWorkflowAction(Action action) {
+		this.action = action;
+	}
+
+	public void setFileManager(FileManager fm) {
+		this.fm = fm;
+	}
+
+	public void setIcon(ComponentServiceIcon icon) {
+		this.icon = icon;
+	}
+
+	public void setUtils(Utils utils) {
+		this.utils = utils;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new ComponentCloseAction(action, fm, icon, utils);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCopyAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCopyAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCopyAction.java
new file mode 100644
index 0000000..676a6c9
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCopyAction.java
@@ -0,0 +1,164 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu.component;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.WEST;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.OK_CANCEL_OPTION;
+import static javax.swing.JOptionPane.OK_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+import javax.swing.JPanel;
+import javax.swing.border.TitledBorder;
+
+import org.apache.log4j.Logger;
+import io.github.taverna_extras.component.api.Component;
+import io.github.taverna_extras.component.api.ComponentException;
+import io.github.taverna_extras.component.api.Family;
+import io.github.taverna_extras.component.api.Version;
+import io.github.taverna_extras.component.api.profile.Profile;
+import io.github.taverna_extras.component.ui.panel.ComponentChoiceMessage;
+import io.github.taverna_extras.component.ui.panel.ComponentChooserPanel;
+import io.github.taverna_extras.component.ui.panel.ProfileChoiceMessage;
+import io.github.taverna_extras.component.ui.panel.RegistryAndFamilyChooserPanel;
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceProviderConfig;
+import io.github.taverna_extras.component.ui.util.Utils;
+import org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+
+/**
+ * @author alanrw
+ */
+public class ComponentCopyAction extends AbstractAction {
+	private static final long serialVersionUID = -4440978712410081685L;
+	private static final Logger logger = getLogger(ComponentCopyAction.class);
+	private static final String COPY_COMPONENT = "Copy component...";
+
+	private final ComponentPreference prefs;
+	private final Utils utils;
+
+	public ComponentCopyAction(ComponentPreference pref, ComponentServiceIcon icon, Utils utils) {
+		super(COPY_COMPONENT, icon.getIcon());
+		this.prefs = pref;
+		this.utils = utils;
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent arg0) {
+		JPanel overallPanel = new JPanel();
+		overallPanel.setLayout(new GridBagLayout());
+
+		GridBagConstraints gbc = new GridBagConstraints();
+
+		ComponentChooserPanel source = new ComponentChooserPanel(prefs);
+		source.setBorder(new TitledBorder("Source component"));
+
+		gbc.insets = new Insets(0, 5, 0, 5);
+		gbc.gridx = 0;
+		gbc.gridy = 0;
+		gbc.anchor = WEST;
+		gbc.fill = BOTH;
+		gbc.gridwidth = 2;
+		gbc.weightx = 1;
+		overallPanel.add(source, gbc);
+
+		final RegistryAndFamilyChooserPanel target = new RegistryAndFamilyChooserPanel(prefs);
+		target.setBorder(new TitledBorder("Target family"));
+		gbc.gridy++;
+		overallPanel.add(target, gbc);
+
+		source.addObserver(new Observer<ComponentChoiceMessage>() {
+			@Override
+			public void notify(Observable<ComponentChoiceMessage> sender,
+					ComponentChoiceMessage message) throws Exception {
+				Profile componentProfile = null;
+				Family componentFamily = message.getComponentFamily();
+				if (componentFamily != null)
+					componentProfile = componentFamily.getComponentProfile();
+				ProfileChoiceMessage profileMessage = new ProfileChoiceMessage(
+						componentProfile);
+				target.notify(null, profileMessage);
+			}
+		});
+
+		int answer = showConfirmDialog(null, overallPanel, "Copy Component",
+				OK_CANCEL_OPTION);
+		if (answer == OK_OPTION)
+			doCopy(source.getChosenComponent(), target.getChosenFamily());
+	}
+
+	private void doCopy(Component sourceComponent, Family targetFamily) {
+		if (sourceComponent == null) {
+			showMessageDialog(null, "Unable to determine source component",
+					"Component Copy Problem", ERROR_MESSAGE);
+			return;
+		} else if (targetFamily == null) {
+			showMessageDialog(null, "Unable to determine target family",
+					"Component Copy Problem", ERROR_MESSAGE);
+			return;
+		}
+
+		try {
+			String componentName = sourceComponent.getName();
+			boolean alreadyUsed = targetFamily.getComponent(componentName) != null;
+			if (alreadyUsed)
+				showMessageDialog(null, componentName + " is already used",
+						"Duplicate component name", ERROR_MESSAGE);
+			else {
+				Version targetVersion = doCopy(sourceComponent, targetFamily,
+						componentName);
+				try {
+					utils.refreshComponentServiceProvider(new ComponentServiceProviderConfig(
+							targetVersion.getID()).getConfiguration());
+				} catch (Exception e) {
+					logger.error(e);
+				}
+			}
+		} catch (ComponentException e) {
+			logger.error("failed to copy component", e);
+			showMessageDialog(null,
+					"Unable to create component: " + e.getMessage(),
+					"Component Copy Problem", ERROR_MESSAGE);
+		}
+	}
+
+	private Version doCopy(Component sourceComponent, Family targetFamily,
+			String componentName) throws ComponentException {
+		return targetFamily
+				.createComponentBasedOn(
+						componentName,
+						sourceComponent.getDescription(),
+						sourceComponent
+								.getComponentVersionMap()
+								.get(sourceComponent.getComponentVersionMap()
+										.lastKey()).getImplementation());
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCopyMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCopyMenuAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCopyMenuAction.java
new file mode 100644
index 0000000..3c6db63
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCopyMenuAction.java
@@ -0,0 +1,61 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu.component;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+import io.github.taverna_extras.component.ui.util.Utils;
+
+/**
+ * @author alanrw
+ */
+public class ComponentCopyMenuAction extends AbstractComponentMenuAction {
+	private static final URI COPY_COMPONENT_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#componentCopy");
+
+	private ComponentPreference prefs;
+	private ComponentServiceIcon icon;
+	private Utils utils;
+
+	public ComponentCopyMenuAction() {
+		super(800, COPY_COMPONENT_URI);
+	}
+	
+	public void setIcon(ComponentServiceIcon icon) {
+		this.icon = icon;
+	}
+
+	public void setPreferences(ComponentPreference prefs) {//FIXME beaninject
+		this.prefs = prefs;
+	}
+
+	public void setUtils(Utils utils) {
+		this.utils = utils;
+	}
+
+	@Override
+	protected Action createAction() {
+		return new ComponentCopyAction(prefs, icon, utils);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCreatorSupport.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCreatorSupport.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCreatorSupport.java
new file mode 100644
index 0000000..ffe26d8
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentCreatorSupport.java
@@ -0,0 +1,257 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu.component;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.OK_CANCEL_OPTION;
+import static javax.swing.JOptionPane.OK_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import io.github.taverna_extras.component.api.Component;
+import io.github.taverna_extras.component.api.ComponentException;
+import io.github.taverna_extras.component.api.ComponentFactory;
+import io.github.taverna_extras.component.api.Version;
+import io.github.taverna_extras.component.ui.ComponentActivityConfigurationBean;
+import io.github.taverna_extras.component.ui.panel.RegistryAndFamilyChooserComponentEntryPanel;
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceProviderConfig;
+import io.github.taverna_extras.component.ui.util.Utils;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.api.core.DataLink;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.scufl2.api.core.Workflow;
+import org.apache.taverna.scufl2.api.port.InputProcessorPort;
+import org.apache.taverna.scufl2.api.port.InputWorkflowPort;
+import org.apache.taverna.scufl2.api.port.OutputProcessorPort;
+import org.apache.taverna.scufl2.api.port.OutputWorkflowPort;
+import org.apache.taverna.workbench.edits.CompoundEdit;
+import org.apache.taverna.workbench.edits.Edit;
+import org.apache.taverna.workbench.edits.EditException;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.exceptions.SaveException;
+import org.apache.taverna.workbench.selection.SelectionManager;
+import org.apache.taverna.workflow.edits.AddActivityEdit;
+import org.apache.taverna.workflow.edits.AddActivityInputPortMappingEdit;
+import org.apache.taverna.workflow.edits.AddActivityOutputPortMappingEdit;
+import org.apache.taverna.workflow.edits.AddDataLinkEdit;
+import org.apache.taverna.workflow.edits.AddProcessorEdit;
+import org.apache.taverna.workflow.edits.AddWorkflowInputPortEdit;
+import org.apache.taverna.workflow.edits.AddWorkflowOutputPortEdit;
+import org.apache.taverna.workflow.edits.RemoveActivityEdit;
+import org.apache.taverna.workflow.edits.RenameEdit;
+import org.apache.taverna.workflowmodel.processor.activity.NestedDataflow;
+import org.apache.taverna.workflowmodel.utils.Tools;
+
+public class ComponentCreatorSupport {
+	private static final Logger logger = getLogger(ComponentCreatorSupport.class);
+
+	private ComponentFactory factory;
+	private FileManager fm;
+	private EditManager em;
+	private ComponentPreference prefs;
+	private FileType ft;
+	private SelectionManager sm;
+
+	public void setComponentFactory(ComponentFactory factory) {
+		this.factory = factory;
+	}
+
+	public void setPreferences(ComponentPreference pref) {
+		this.prefs = pref;
+	}
+
+	public void setFileManager(FileManager fm) {
+		this.fm = fm;
+	}
+
+	public void setEditManager(EditManager em) {
+		this.em = em;
+	}
+
+	public void setFileType(FileType ft) {
+		this.ft = ft;
+	}
+
+	public void setSelectionManager(SelectionManager sm) {
+		this.sm = sm;
+	}
+
+	public class CopiedProcessor {
+		Processor processor;
+		Map<String,Workflow> requiredSubworkflows;
+	}
+
+	void moveComponentActivityIntoPlace(Activity toReplace, Processor contextProcessor,
+			Activity replacingActivity) throws EditException {
+		List<Edit<?>> editsToDo = new ArrayList<>();
+		for (InputProcessorPort pip : contextProcessor.getInputPorts())
+			editsToDo.add(new AddActivityInputPortMappingEdit(toReplace, pip, null/*FIXME*/));
+		for (OutputProcessorPort pop : contextProcessor.getOutputPorts())
+			editsToDo.add(new AddActivityOutputPortMappingEdit(toReplace, pop, null/*FIXME*/));
+		editsToDo.add(new RemoveActivityEdit(contextProcessor, toReplace));
+		editsToDo.add(new AddActivityEdit(contextProcessor, replacingActivity));
+		em.doDataflowEdit(contextProcessor.getParent().getParent(),
+				new CompoundEdit(editsToDo));
+	}
+
+	void connectNewProcessor(Workflow d, Processor newProcessor)
+			throws EditException {
+		List<Edit<?>> editsToDo = new ArrayList<>();
+
+		for (InputProcessorPort pip : newProcessor.getInputPorts()) {
+			InputWorkflowPort dip = new InputWorkflowPort(d, pip.getName());
+			// FIXME How to set depth?
+			editsToDo.add(new AddWorkflowInputPortEdit(d, dip));
+			editsToDo.add(new AddDataLinkEdit(d, new DataLink(d, dip, pip)));
+		}
+
+		for (OutputProcessorPort pop : newProcessor.getOutputPorts()) {
+			OutputWorkflowPort dop = new OutputWorkflowPort(d, pop.getName());
+			// TODO How to indicate depth?
+			editsToDo.add(new AddWorkflowOutputPortEdit(d, dop));
+			editsToDo.add(new AddDataLinkEdit(d, new DataLink(d, pop, dop)));
+		}
+		em.doDataflowEdit(d.getParent(), new CompoundEdit(editsToDo));
+	}
+
+	public ComponentActivityConfigurationBean saveWorkflowAsComponent(
+			WorkflowBundle d, Version.ID ident) throws SaveException, IOException,
+			ComponentException {
+		if (ident == null)
+			return null;
+
+		createInitialComponent(d, ident);
+
+		Utils.refreshComponentServiceProvider(new ComponentServiceProviderConfig(
+				ident));
+		return new ComponentActivityConfigurationBean(ident, factory);
+	}
+
+	public ComponentActivityConfigurationBean saveWorkflowAsComponent(
+			Workflow d, Version.ID ident) throws SaveException, IOException,
+			ComponentException {
+		WorkflowBundle b = new WorkflowBundle();
+		((Workflow)d.clone()).setParent(b);
+		//FIXME also must copy profile parts!
+		return saveWorkflowAsComponent(b, ident);
+	}
+
+	Version.ID getNewComponentIdentification(String defaultName) {
+		RegistryAndFamilyChooserComponentEntryPanel panel = new RegistryAndFamilyChooserComponentEntryPanel(prefs);
+		panel.setComponentName(defaultName);
+		int result = showConfirmDialog(null, panel, "Component location",
+				OK_CANCEL_OPTION);
+		if (result != OK_OPTION)
+			return null;
+
+		Version.ID ident = panel.getComponentVersionIdentification();
+		if (ident == null) {
+			showMessageDialog(null,
+					"Not enough information to create component",
+					"Component creation problem", ERROR_MESSAGE);
+			return null;
+		}
+
+		try {
+			Component existingComponent = factory.getComponent(ident);
+			if (existingComponent != null) {
+				showMessageDialog(null,
+						"Component with this name already exists",
+						"Component creation problem", ERROR_MESSAGE);
+				return null;
+			}
+		} catch (ComponentException e) {
+			logger.error("failed to search registry", e);
+			showMessageDialog(null,
+					"Problem searching registry: " + e.getMessage(),
+					"Component creation problem", ERROR_MESSAGE);
+			return null;
+		}
+		return ident;
+	}
+
+	CopiedProcessor copyProcessor(Processor p) throws IOException {
+		CopiedProcessor copy = new CopiedProcessor();
+		copy.processor = ProcessorXMLSerializer.getInstance().processorToXML(p);
+		copy.requiredSubworkflows = new HashMap<>();
+		rememberSubworkflows(p, copy);
+		return copy;
+	}
+
+	void rememberSubworkflows(Processor p, CopiedProcessor copy) {
+		for (Activity a : p.getActivity(sm.getSelectedProfile()))
+			if (a instanceof NestedDataflow) {
+				NestedDataflow da = (NestedDataflow) a;
+				Workflow df = da.getNestedDataflow();
+				if (!copy.requiredSubworkflows.containsKey(df.getIdentifier())) {
+					copy.requiredSubworkflows.put(df.getIdentifier(),
+							DataflowXMLSerializer.getInstance()
+									.serializeDataflow(df));
+					for (Processor sp : df.getProcessors())
+						rememberSubworkflows(sp, copy);
+				}
+			}
+	}
+
+	public Processor pasteProcessor(CopiedProcessor copy, Workflow d)
+			throws 
+			ClassNotFoundException, InstantiationException,
+			IllegalAccessException, EditException {
+		Processor result = ProcessorXMLDeserializer.getInstance()
+				.deserializeProcessor(copy.processor, copy.requiredSubworkflows);
+		if (result == null)
+			return null;
+
+		String newName = Tools.uniqueProcessorName(result.getName(), d);
+
+		List<Edit<?>> editList = new ArrayList<>();
+		if (!newName.equals(result.getName()))
+			editList.add(new RenameEdit<>(result, newName));
+		editList.add(new AddProcessorEdit(d, result));
+		em.doDataflowEdit(d.getParent(), new CompoundEdit(editList));
+
+		return result;
+	}
+
+	public Version.ID createInitialComponent(WorkflowBundle d, Version.ID ident)
+			throws ComponentException {
+		try {
+			fm.saveDataflow(d, ft, ident, false);
+
+			em.doDataflowEdit(d, new RenameEdit<>(d, d.getName()));
+		} catch (SaveException | IllegalStateException | EditException e) {
+			throw new ComponentException(e);
+		}
+		return ident;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentDeleteAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentDeleteAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentDeleteAction.java
new file mode 100644
index 0000000..72141c9
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/component/ComponentDeleteAction.java
@@ -0,0 +1,143 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.menu.component;
+
+import static java.lang.String.format;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.OK_CANCEL_OPTION;
+import static javax.swing.JOptionPane.OK_OPTION;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.YES_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.event.ActionEvent;
+import java.util.concurrent.ExecutionException;
+
+import javax.swing.AbstractAction;
+import javax.swing.SwingWorker;
+
+import org.apache.log4j.Logger;
+import io.github.taverna_extras.component.api.Component;
+import io.github.taverna_extras.component.api.ComponentException;
+import io.github.taverna_extras.component.api.Version;
+import io.github.taverna_extras.component.ui.panel.ComponentChooserPanel;
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceIcon;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceProviderConfig;
+import io.github.taverna_extras.component.ui.util.Utils;
+
+import org.apache.taverna.scufl2.api.configurations.Configuration;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.workbench.file.FileManager;
+
+/**
+ * @author alanrw
+ */
+public class ComponentDeleteAction extends AbstractAction {
+	private static final String COMPONENT_PROBLEM_TITLE = "Component Problem";
+	private static final String CONFIRM_MSG = "Are you sure you want to delete %s?";
+	private static final String CONFIRM_TITLE = "Delete Component Confirmation";
+	private static final String DELETE_COMPONENT_LABEL = "Delete component...";
+	private static final String DELETE_FAILED_TITLE = "Component Deletion Error";
+	private static final String FAILED_MSG = "Unable to delete %s: %s";
+	private static final String OPEN_COMPONENT_MSG = "The component is open";
+	private static final String TITLE = "Component choice";
+	private static final String WHAT_COMPONENT_MSG = "Unable to determine component";
+	private static final long serialVersionUID = -2992743162132614936L;
+	private static final Logger logger = getLogger(ComponentDeleteAction.class);
+
+	private final FileManager fm;
+	private final ComponentPreference prefs;
+	private final Utils utils;
+
+	public ComponentDeleteAction(FileManager fm, ComponentPreference prefs,
+			ComponentServiceIcon icon, Utils utils) {
+		super(DELETE_COMPONENT_LABEL, icon.getIcon());
+		this.fm = fm;
+		this.prefs = prefs;
+		this.utils = utils;
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent evt) {
+		ComponentChooserPanel panel = new ComponentChooserPanel(prefs);
+		int answer = showConfirmDialog(null, panel, TITLE, OK_CANCEL_OPTION);
+		if (answer == OK_OPTION)
+			doDelete(panel.getChosenComponent());
+	}
+
+	private void doDelete(final Component chosenComponent) {
+		if (chosenComponent == null) {
+			showMessageDialog(null, WHAT_COMPONENT_MSG,
+					COMPONENT_PROBLEM_TITLE, ERROR_MESSAGE);
+		} else if (componentIsInUse(chosenComponent)) {
+			showMessageDialog(null, OPEN_COMPONENT_MSG,
+					COMPONENT_PROBLEM_TITLE, ERROR_MESSAGE);
+		} else if (showConfirmDialog(null,
+				format(CONFIRM_MSG, chosenComponent.getName()), CONFIRM_TITLE,
+				YES_NO_OPTION) == YES_OPTION)
+			new SwingWorker<Configuration, Object>() {
+				@Override
+				protected Configuration doInBackground() throws Exception {
+					return deleteComponent(chosenComponent);
+				}
+
+				@Override
+				protected void done() {
+					refresh(chosenComponent, this);
+				}
+			}.execute();
+	}
+
+	private Configuration deleteComponent(Component component)
+			throws ComponentException {
+		ComponentServiceProviderConfig config = new ComponentServiceProviderConfig(
+				component.getFamily());
+		component.delete();
+		return config.getConfiguration();
+	}
+
+	protected void refresh(Component component,
+			SwingWorker<Configuration, Object> worker) {
+		try {
+			utils.refreshComponentServiceProvider(worker.get());
+		} catch (ExecutionException e) {
+			logger.error("failed to delete component", e.getCause());
+			showMessageDialog(
+					null,
+					format(FAILED_MSG, component.getName(), e.getCause()
+							.getMessage()), DELETE_FAILED_TITLE, ERROR_MESSAGE);
+		} catch (InterruptedException e) {
+			logger.warn("interrupted during component deletion", e);
+		}
+	}
+
+	private boolean componentIsInUse(Component component) {
+		for (WorkflowBundle d : fm.getOpenDataflows()) {
+			Object dataflowSource = fm.getDataflowSource(d);
+			if (dataflowSource instanceof Version.ID
+					&& ((Version.ID) dataflowSource).mostlyEqualTo(component))
+				return true;
+		}
+		return false;
+	}
+}