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 2015/02/23 11:35:59 UTC

[44/58] [abbrv] incubator-taverna-plugin-component git commit: org.apache.taverna.component.ui

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/SemanticAnnotationUtils.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/SemanticAnnotationUtils.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/SemanticAnnotationUtils.java
new file mode 100644
index 0000000..b59ffcc
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/SemanticAnnotationUtils.java
@@ -0,0 +1,191 @@
+/*******************************************************************************
+ * Copyright (C) 2012 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 org.apache.taverna.component.ui.annotation;
+
+import static com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.apache.taverna.component.api.ComponentException;
+import org.apache.taverna.component.api.profile.Profile;
+import org.apache.taverna.component.api.profile.SemanticAnnotationProfile;
+
+import uk.org.taverna.scufl2.api.annotation.Annotation;
+import uk.org.taverna.scufl2.api.common.AbstractNamed;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+import com.hp.hpl.jena.ontology.OntProperty;
+import com.hp.hpl.jena.ontology.OntResource;
+import com.hp.hpl.jena.rdf.model.Model;
+import com.hp.hpl.jena.rdf.model.RDFNode;
+import com.hp.hpl.jena.rdf.model.Resource;
+import com.hp.hpl.jena.rdf.model.Statement;
+
+/**
+ * @author David Withers
+ */
+public class SemanticAnnotationUtils {
+	protected static final String ENCODING = "TURTLE";
+	/* Pretend-base for making relative URIs */
+	private static String BASE = "widget://4aa8c93c-3212-487c-a505-3e337adf54a3/";
+	private static Logger logger = getLogger(SemanticAnnotationUtils.class);
+
+	public static String getObjectName(Statement statement) {
+		return getDisplayName(statement.getObject());
+	}
+
+	public static String getDisplayName(RDFNode node) {
+		if (node == null)
+			return "unknown";
+		else if (node.isAnon())
+			return "anon";
+		else if (node.isLiteral())
+			return node.asLiteral().getLexicalForm();
+		else if (node.isResource()) {
+			Resource resource = node.asResource();
+			if (resource instanceof OntResource) {
+				String label = ((OntResource) resource).getLabel(null);
+				if (label != null)
+					return label;
+			}
+			String localName = resource.getLocalName();
+			if ((localName != null) && !localName.isEmpty())
+				return localName;
+			return resource.toString();
+		} else
+			return "unknown";
+	}
+
+	public static Annotation findSemanticAnnotation(AbstractNamed annotated) {
+		for (Annotation annotation : annotated.getAnnotations())
+			return annotation;
+		return null;
+	}
+
+	public static String getStrippedAnnotationContent(Annotation annotation)
+			throws IOException {
+		AbstractNamed target = (AbstractNamed) annotation.getTarget();
+		return annotation.getRDFContent().replace(
+				target.getRelativeURI(annotation).toASCIIString(), BASE);
+	}
+
+	public static Annotation createSemanticAnnotation(WorkflowBundle bundle,
+			AbstractNamed target, Model model) throws IOException {
+		Calendar now = new GregorianCalendar();
+		Annotation annotation = new Annotation();
+		annotation.setParent(bundle);
+		String path = annotation.getResourcePath();
+		annotation.setTarget(target);
+		// annotation.setAnnotatedBy(annotatedBy);
+		annotation.setAnnotatedAt(now);
+		// annotation.setSerializedBy(serializedBy);
+		annotation.setSerializedAt(now);
+		bundle.getResources().addResource(
+				"@base<" + target.getRelativeURI(annotation).toASCIIString()
+						+ "> .\n" + createTurtle(model), path, "text/rdf+n3");
+		return annotation;
+	}
+
+	/**
+	 * @param model
+	 * @return
+	 */
+	public static String createTurtle(Model model) {
+		StringWriter stringWriter = new StringWriter();
+		model.write(stringWriter, ENCODING, BASE);
+		// Workaround for https://issues.apache.org/jira/browse/JENA-132
+		return stringWriter.toString().replace(BASE, "");
+	}
+
+	public static Model populateModel(WorkflowBundle annotated) {
+		Model result = createDefaultModel();
+		try {
+			for (Annotation a : annotated.getAnnotations())
+				populateModelFromString(result, a.getRDFContent());
+		} catch (Exception e) {
+			logger.error("failed to construct semantic annotation model", e);
+		}
+		return result;
+	}
+
+	public static void populateModel(Model result, Annotation annotation)
+			throws IOException {
+		AbstractNamed target = (AbstractNamed) annotation.getTarget();
+		String content = annotation.getRDFContent().replace(
+				target.getRelativeURI(annotation).toASCIIString(), BASE);
+		populateModelFromString(result, content);
+	}
+
+	public static void populateModelFromString(Model result, String content) {
+		result.read(new StringReader(content), BASE, ENCODING);
+	}
+
+	public static Resource createBaseResource(Model model) {
+		return model.createResource(BASE);
+	}
+
+	/**
+	 * Check if a profile is satisfied by a component.
+	 * 
+	 * @param bundle
+	 *            The component definition.
+	 * @param componentProfile
+	 *            The profile definition.
+	 * @return The set of failed constraints. If empty, the profile is satisfied
+	 *         by the component.
+	 */
+	public static Set<SemanticAnnotationProfile> checkComponent(
+			WorkflowBundle bundle, Profile componentProfile) {
+		// TODO Check port presence by name
+		Set<SemanticAnnotationProfile> problemProfiles = new HashSet<>();
+		Model model = populateModel(bundle);
+		Set<Statement> statements = model.listStatements().toSet();
+		try {
+			for (SemanticAnnotationProfile saProfile : componentProfile
+					.getSemanticAnnotations()) {
+				OntProperty predicate = saProfile.getPredicate();
+				if (predicate == null)
+					continue;
+				int count = 0;
+				for (Statement statement : statements)
+					if (statement.getPredicate().equals(predicate))
+						count++;
+				if (count < saProfile.getMinOccurs())
+					problemProfiles.add(saProfile);
+				if (saProfile.getMaxOccurs() != null
+						&& count > saProfile.getMaxOccurs())
+					// The UI should prevent this, but check anyway
+					problemProfiles.add(saProfile);
+			}
+		} catch (ComponentException e) {
+			logger.error("failed to look up profiles for semantic annotations", e);
+		}
+		return problemProfiles;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/TurtleContextualView.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/TurtleContextualView.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/TurtleContextualView.java
new file mode 100644
index 0000000..586c557
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/TurtleContextualView.java
@@ -0,0 +1,78 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.annotation;
+
+import static java.awt.BorderLayout.CENTER;
+import static org.apache.taverna.component.ui.annotation.SemanticAnnotationUtils.findSemanticAnnotation;
+import static org.apache.taverna.component.ui.annotation.SemanticAnnotationUtils.getStrippedAnnotationContent;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.awt.BorderLayout;
+import java.io.IOException;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.ContextualView;
+
+import org.slf4j.Logger;
+
+import uk.org.taverna.scufl2.api.annotation.Annotation;
+import uk.org.taverna.scufl2.api.common.AbstractNamed;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ */
+public class TurtleContextualView extends ContextualView {
+	private static final long serialVersionUID = -3401885589263647202L;
+	private static final Logger log = getLogger(TurtleContextualView.class);
+	private JPanel panel;
+	private String annotationContent = "";
+
+	public TurtleContextualView(AbstractNamed selection, WorkflowBundle bundle)  {
+		Annotation annotation = findSemanticAnnotation(selection);
+		try {
+			if (annotation != null)
+				annotationContent = getStrippedAnnotationContent(annotation);
+		} catch (IOException e) {
+			log.info("failed to read semantic annotation; using empty string", e);
+		}
+		initialise();
+		initView();
+	}
+
+	@Override
+	public JComponent getMainFrame() {
+		return panel;
+	}
+
+	@Override
+	public int getPreferredPosition() {
+		return 512;
+	}
+
+	@Override
+	public String getViewTitle() {
+		return "Turtle representation";
+	}
+
+	@Override
+	public void refreshView() {
+		initialise();
+	}
+
+	protected final void initialise() {
+		if (panel == null)
+			panel = new JPanel(new BorderLayout());
+		else
+			panel.removeAll();
+		JTextArea textArea = new JTextArea(20, 80);
+		textArea.setEditable(false);
+		textArea.setText(annotationContent);
+		panel.add(textArea, CENTER);
+		revalidate();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/TurtleInputPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/TurtleInputPanel.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/TurtleInputPanel.java
new file mode 100644
index 0000000..30a4c9b
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/TurtleInputPanel.java
@@ -0,0 +1,90 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.annotation;
+
+import static com.hp.hpl.jena.rdf.model.ModelFactory.createOntologyModel;
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.EAST;
+import static java.awt.BorderLayout.SOUTH;
+import static org.apache.taverna.component.ui.annotation.SemanticAnnotationUtils.populateModelFromString;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.lang.ui.DeselectingButton;
+import net.sf.taverna.t2.lang.ui.ReadOnlyTextArea;
+
+import com.hp.hpl.jena.ontology.Individual;
+import com.hp.hpl.jena.ontology.OntClass;
+import com.hp.hpl.jena.ontology.OntModel;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+public class TurtleInputPanel extends JPanel {
+	JTextArea turtleTextArea = new JTextArea(30, 80);
+	ReadOnlyTextArea errors = new ReadOnlyTextArea(1, 80);
+	private OntClass clazz;
+
+	public TurtleInputPanel(OntClass clazz) {
+		super(new BorderLayout());
+		this.clazz = clazz;
+
+		add(new JScrollPane(turtleTextArea), CENTER);
+
+		turtleTextArea.setText("<#changeme> a <" + clazz.getURI() + ">\n\n\n.");
+
+		JPanel buttonPanel = new JPanel();
+		buttonPanel.setLayout(new BorderLayout());
+		JButton validateButton = new DeselectingButton(new AbstractAction(
+				"Validate") {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				getContentAsModel();
+			}
+		});
+		buttonPanel.add(errors, CENTER);
+		errors.setOpaque(false);
+		buttonPanel.add(validateButton, EAST);
+		add(buttonPanel, SOUTH);
+	}
+
+	public OntModel getContentAsModel() {
+		OntModel result = createOntologyModel();
+		try {
+			populateModelFromString(result, getContentAsString());
+
+			// Check it is not still called changeme
+			List<Individual> individuals = result.listIndividuals(clazz)
+					.toList();
+			if (individuals.isEmpty()) {
+				errors.setText("No valid individuals");
+				return null;
+			}
+			for (Individual i : individuals)
+				if (i.getURI().endsWith("changeme")) {
+					errors.setText("Name has not been changed");
+					return null;
+				}
+
+			errors.setText("No errors found");
+			return result;
+		} catch (Throwable ex) { // syntax error?
+			errors.setText(ex.getMessage());
+			return null;
+		}
+	}
+
+	public String getContentAsString() {
+		return turtleTextArea.getText();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/UnrecognizedStatementPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/UnrecognizedStatementPanel.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/UnrecognizedStatementPanel.java
new file mode 100644
index 0000000..e16c30d
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/UnrecognizedStatementPanel.java
@@ -0,0 +1,27 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.annotation;
+
+import static java.lang.String.format;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import com.hp.hpl.jena.rdf.model.Statement;
+
+/**
+ * @author alanrw
+ * 
+ */
+@SuppressWarnings("serial")
+public class UnrecognizedStatementPanel extends JPanel {
+	public UnrecognizedStatementPanel(Statement statement) {
+		setLayout(new BorderLayout());
+		setBorder(new GreyBorder());
+		add(new JLabel(format("Unable to find %s in the profile",
+				statement.getPredicate())));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/UnresolveablePredicatePanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/UnresolveablePredicatePanel.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/UnresolveablePredicatePanel.java
new file mode 100644
index 0000000..454986b
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/annotation/UnresolveablePredicatePanel.java
@@ -0,0 +1,27 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.annotation;
+
+import static java.lang.String.format;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.apache.taverna.component.api.profile.SemanticAnnotationProfile;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+public class UnresolveablePredicatePanel extends JPanel {
+	public UnresolveablePredicatePanel(
+			SemanticAnnotationProfile semanticAnnotationProfile) {
+		setLayout(new BorderLayout());
+		setBorder(new GreyBorder());
+		add(new JLabel(format("Unable to resolve %s in the ontology",
+				semanticAnnotationProfile.getPredicateString())));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/config/ComponentConfigurationPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/config/ComponentConfigurationPanel.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/config/ComponentConfigurationPanel.java
new file mode 100644
index 0000000..293a8bc
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/config/ComponentConfigurationPanel.java
@@ -0,0 +1,153 @@
+package org.apache.taverna.component.ui.config;
+
+import static java.awt.event.ItemEvent.SELECTED;
+import static org.apache.log4j.Logger.getLogger;
+import static org.apache.taverna.component.api.config.ComponentPropertyNames.COMPONENT_NAME;
+import static org.apache.taverna.component.api.config.ComponentPropertyNames.COMPONENT_VERSION;
+import static org.apache.taverna.component.api.config.ComponentPropertyNames.FAMILY_NAME;
+import static org.apache.taverna.component.api.config.ComponentPropertyNames.REGISTRY_BASE;
+import static org.apache.taverna.component.ui.util.Utils.SHORT_STRING;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.SortedMap;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationPanel;
+
+import org.apache.log4j.Logger;
+import org.apache.taverna.component.api.Component;
+import org.apache.taverna.component.api.ComponentException;
+import org.apache.taverna.component.api.ComponentFactory;
+import org.apache.taverna.component.api.Version;
+import org.apache.taverna.component.ui.panel.ComponentListCellRenderer;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+@SuppressWarnings("serial")
+public class ComponentConfigurationPanel extends ActivityConfigurationPanel {
+	private static Logger logger = getLogger(ComponentConfigurationPanel.class);
+
+	private ComponentFactory factory;//FIXME beaninject
+	private ServiceRegistry sr;
+
+	private final JComboBox<Object> componentVersionChoice = new JComboBox<>();
+
+	public ComponentConfigurationPanel(Activity activity,
+			ComponentFactory factory, ServiceRegistry serviceRegistry) {
+		super(activity);
+		sr = serviceRegistry;
+		this.factory = factory;
+		componentVersionChoice.setPrototypeDisplayValue(SHORT_STRING);
+		initGui();
+	}
+
+	private Version getSelectedVersion() {
+		return (Version) componentVersionChoice.getSelectedItem();
+	}
+	private URI getRegistryBase() {
+		return URI.create(getProperty(REGISTRY_BASE));
+	}
+	private String getFamilyName() {
+		return getProperty(FAMILY_NAME);
+	}
+	private String getComponentName() {
+		return getProperty(COMPONENT_NAME);
+	}
+	private Integer getComponentVersion() {
+		return Integer.parseInt(getProperty(COMPONENT_VERSION));
+	}
+
+	protected void initGui() {
+		removeAll();
+		setLayout(new GridLayout(0, 2));
+
+		componentVersionChoice.setRenderer(new ComponentListCellRenderer<>());
+		componentVersionChoice.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent event) {
+				if (event.getStateChange() == SELECTED)
+					updateToolTipText();
+			}
+		});
+		updateComponentVersionChoice();
+
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.insets = new Insets(0, 5, 0, 5);
+		gbc.gridx = 0;
+		gbc.anchor = GridBagConstraints.WEST;
+		gbc.fill = GridBagConstraints.HORIZONTAL;
+		gbc.gridy = 2;
+		this.add(new JLabel("Component version:"), gbc);
+		gbc.gridx = 1;
+		gbc.weightx = 1;
+		this.add(componentVersionChoice, gbc);
+
+		// Populate fields from activity configuration bean
+		refreshConfiguration();
+	}
+
+	/**
+	 * Check that user values in UI are valid
+	 */
+	@Override
+	public boolean checkValues() {
+		return true;
+	}
+
+	/**
+	 * Check if the user has changed the configuration from the original
+	 */
+	@Override
+	public boolean isConfigurationChanged() {
+		return !getSelectedVersion().getVersionNumber().equals(
+				getComponentVersion());
+	}
+
+	/**
+	 * Prepare a new configuration bean from the UI, to be returned with
+	 * getConfiguration()
+	 */
+	@Override
+	public void noteConfiguration() {
+		setProperty(COMPONENT_VERSION, getSelectedVersion().getVersionNumber()
+				.toString());
+		//FIXME is this right at all???
+		configureInputPorts(sr);
+		configureOutputPorts(sr);
+	}
+
+	private void updateComponentVersionChoice() {
+		Component component;
+		componentVersionChoice.removeAllItems();
+		componentVersionChoice.setToolTipText(null);
+		try {
+			component = factory.getComponent(getRegistryBase().toURL(),
+					getFamilyName(), getComponentName());
+		} catch (ComponentException | MalformedURLException e) {
+			logger.error("failed to get component", e);
+			return;
+		}
+		SortedMap<Integer, Version> componentVersionMap = component
+				.getComponentVersionMap();
+		for (Version v : componentVersionMap.values())
+			componentVersionChoice.addItem(v);
+		componentVersionChoice.setSelectedItem(componentVersionMap
+				.get(getComponentVersion()));
+		updateToolTipText();
+	}
+
+	private void updateToolTipText() {
+		Version selectedVersion = (Version) componentVersionChoice
+				.getSelectedItem();
+		componentVersionChoice.setToolTipText(selectedVersion.getDescription());
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/config/ComponentConfigureAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/config/ComponentConfigureAction.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/config/ComponentConfigureAction.java
new file mode 100644
index 0000000..f7252cc
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/config/ComponentConfigureAction.java
@@ -0,0 +1,50 @@
+package org.apache.taverna.component.ui.config;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import org.apache.taverna.component.api.ComponentFactory;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.ActivityConfigurationAction;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationDialog;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+@SuppressWarnings("serial")
+public class ComponentConfigureAction extends ActivityConfigurationAction {
+	private EditManager editManager;
+	private FileManager fileManager;
+	private ServiceRegistry serviceRegistry;
+	private ComponentFactory factory;
+
+	public ComponentConfigureAction(Activity activity, Frame owner,
+			ComponentFactory factory, ActivityIconManager activityIconManager,
+			ServiceDescriptionRegistry serviceDescriptionRegistry,
+			EditManager editManager, FileManager fileManager,
+			ServiceRegistry serviceRegistry) {
+		super(activity, activityIconManager, serviceDescriptionRegistry);
+		this.editManager = editManager;
+		this.fileManager = fileManager;
+		this.serviceRegistry = serviceRegistry;
+		this.factory = factory;
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		ActivityConfigurationDialog currentDialog = getDialog(getActivity());
+		if (currentDialog != null) {
+			currentDialog.toFront();
+			return;
+		}
+
+		ComponentConfigurationPanel configView = new ComponentConfigurationPanel(
+				activity, factory, serviceRegistry);
+		ActivityConfigurationDialog dialog = new ActivityConfigurationDialog(
+				getActivity(), configView, editManager);
+		setDialog(getActivity(), dialog, fileManager);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentDataflowHealthCheckExplainer.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentDataflowHealthCheckExplainer.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentDataflowHealthCheckExplainer.java
new file mode 100644
index 0000000..cbc046d
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentDataflowHealthCheckExplainer.java
@@ -0,0 +1,75 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.file;
+
+import static java.util.Collections.sort;
+import static org.apache.taverna.component.ui.annotation.SemanticAnnotationUtils.getDisplayName;
+import static org.apache.taverna.component.ui.util.ComponentHealthCheck.FAILS_PROFILE;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+
+import org.apache.taverna.component.api.profile.SemanticAnnotationProfile;
+import org.apache.taverna.component.ui.util.ComponentHealthCheck;
+
+import net.sf.taverna.t2.visit.VisitKind;
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.workbench.report.explainer.VisitExplainer;
+
+/**
+ * @author alanrw
+ */
+public class ComponentDataflowHealthCheckExplainer implements VisitExplainer {
+	private static final Comparator<SemanticAnnotationProfile> comparator = new Comparator<SemanticAnnotationProfile>() {
+		@Override
+		public int compare(SemanticAnnotationProfile a,
+				SemanticAnnotationProfile b) {
+			return getDisplayName(a.getPredicate()).compareTo(
+					getDisplayName(b.getPredicate()));
+		}
+	};
+
+	@Override
+	public boolean canExplain(VisitKind vk, int resultId) {
+		return vk instanceof ComponentHealthCheck
+				&& resultId == FAILS_PROFILE;
+	}
+
+	@Override
+	public JComponent getExplanation(VisitReport vr) {
+		@SuppressWarnings("unchecked")
+		Set<SemanticAnnotationProfile> problemProfiles = (Set<SemanticAnnotationProfile>) vr
+				.getProperty("problemProfiles");
+		List<SemanticAnnotationProfile> sortedList = new ArrayList<>(
+				problemProfiles);
+		sort(sortedList, comparator);
+		StringBuilder text = new StringBuilder();
+		for (SemanticAnnotationProfile profile : sortedList)
+			text.append(getSemanticProfileExplanation(profile)).append("\n");
+		return new JTextArea(text.toString());
+	}
+
+	@Override
+	public JComponent getSolution(VisitReport vr) {
+		return new JTextArea("Correct the semantic annotation");
+	}
+
+	private static String getSemanticProfileExplanation(
+			SemanticAnnotationProfile p) {
+		Integer minOccurs = p.getMinOccurs();
+		Integer maxOccurs = p.getMaxOccurs();
+		String displayName = getDisplayName(p.getPredicate());
+		if (maxOccurs == null)
+			return displayName + " must have at least " + minOccurs + " value";
+		if (minOccurs.equals(maxOccurs))
+			return displayName + " must have " + minOccurs + " value(s)";
+		return displayName + " must have between " + minOccurs + " and "
+				+ maxOccurs + " value(s)";
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentDataflowHealthChecker.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentDataflowHealthChecker.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentDataflowHealthChecker.java
new file mode 100644
index 0000000..b9076d5
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentDataflowHealthChecker.java
@@ -0,0 +1,89 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.file;
+
+import static net.sf.taverna.t2.visit.VisitReport.Status.SEVERE;
+import static org.apache.log4j.Logger.getLogger;
+import static org.apache.taverna.component.ui.annotation.SemanticAnnotationUtils.checkComponent;
+import static org.apache.taverna.component.ui.util.ComponentHealthCheck.FAILS_PROFILE;
+
+import java.util.List;
+import java.util.Set;
+
+import net.sf.taverna.t2.visit.VisitReport;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workflowmodel.health.HealthChecker;
+
+import org.apache.log4j.Logger;
+import org.apache.taverna.component.api.ComponentException;
+import org.apache.taverna.component.api.ComponentFactory;
+import org.apache.taverna.component.api.Family;
+import org.apache.taverna.component.api.Version;
+import org.apache.taverna.component.api.profile.SemanticAnnotationProfile;
+import org.apache.taverna.component.ui.util.ComponentHealthCheck;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author alanrw
+ */
+public class ComponentDataflowHealthChecker implements HealthChecker<Dataflow> {
+	private static final String PROFILE_UNSATISFIED_MSG = "Workflow does not satisfy component profile";
+	private static Logger logger = getLogger(ComponentDataflowHealthChecker.class);
+
+	private FileManager fm;
+	private ComponentHealthCheck visitType = ComponentHealthCheck.getInstance(); //FIXME beaninject?
+	private ComponentFactory factory;
+
+	public void setComponentFactory(ComponentFactory factory) {
+		this.factory = factory;
+	}
+
+	public void setFileManager(FileManager fm) {
+		this.fm = fm;
+	}
+
+	private Version.ID getSource(Object o) {
+		return (Version.ID) fm.getDataflowSource((WorkflowBundle) o);
+	}
+
+	public void checkProfileSatisfied(WorkflowBundle bundle) {
+		//FIXME
+	}
+	@Override
+	public boolean canVisit(Object o) {
+		try {
+			return getSource(o) != null;
+		} catch (IllegalArgumentException e) {
+			// Not open?
+		} catch (ClassCastException e) {
+			// Not dataflow? Not component?
+		}
+		return false;
+	}
+
+	@Override
+	public VisitReport visit(WorkflowBundle dataflow, List<Object> ancestry) {
+		try {
+			Version.ID ident = getSource(dataflow);
+			Family family = factory.getFamily(ident.getRegistryBase(),
+					ident.getFamilyName());
+
+			Set<SemanticAnnotationProfile> problemProfiles = checkComponent(
+					dataflow, family.getComponentProfile());
+			if (problemProfiles.isEmpty())
+				return null;
+
+			VisitReport visitReport = new VisitReport(visitType, dataflow,
+					PROFILE_UNSATISFIED_MSG, FAILS_PROFILE, SEVERE);
+			visitReport.setProperty("problemProfiles", problemProfiles);
+			return visitReport;
+		} catch (ComponentException e) {
+			logger.error(
+					"failed to comprehend profile while checking for match", e);
+			return null;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentOpener.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentOpener.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentOpener.java
new file mode 100644
index 0000000..4e07171
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentOpener.java
@@ -0,0 +1,73 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.file;
+
+import static org.apache.log4j.Logger.getLogger;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+
+import org.apache.log4j.Logger;
+import org.apache.taverna.component.api.ComponentException;
+import org.apache.taverna.component.api.ComponentFactory;
+import org.apache.taverna.component.api.Version;
+import org.apache.taverna.component.api.Version.ID;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author alanrw
+ */
+public class ComponentOpener extends AbstractDataflowPersistenceHandler
+		implements DataflowPersistenceHandler {
+	private static Logger logger = getLogger(ComponentOpener.class);
+
+	private ComponentFactory factory;
+	private FileType fileType;
+
+	public void setComponentFactory(ComponentFactory factory) {
+		this.factory = factory;
+	}
+
+	public void setFileType(FileType fileType) {
+		this.fileType = fileType;
+	}
+	
+	@Override
+	public DataflowInfo openDataflow(FileType fileType, Object source)
+			throws OpenException {
+		if (!getOpenFileTypes().contains(fileType))
+			throw new IllegalArgumentException("Unsupported file type "
+					+ fileType);
+		if (!(source instanceof Version.ID))
+			throw new IllegalArgumentException("Unsupported source type "
+					+ source.getClass().getName());
+
+		WorkflowBundle d;
+		try {
+			d = factory.getVersion((ID) source).getImplementation();
+		} catch (ComponentException e) {
+			logger.error("Unable to read dataflow", e);
+			throw new OpenException("Unable to read dataflow", e);
+		}
+		return new DataflowInfo(fileType, source, d, new Date());
+	}
+
+	@Override
+	public List<FileType> getOpenFileTypes() {
+		return Arrays.<FileType> asList(fileType);
+	}
+
+	@Override
+	public List<Class<?>> getOpenSourceTypes() {
+		return Arrays.<Class<?>> asList(Version.ID.class);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentSaver.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentSaver.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentSaver.java
new file mode 100644
index 0000000..424961a
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/ComponentSaver.java
@@ -0,0 +1,170 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.file;
+
+import static javax.swing.JOptionPane.OK_CANCEL_OPTION;
+import static javax.swing.JOptionPane.OK_OPTION;
+import static javax.swing.JOptionPane.showConfirmDialog;
+import static org.apache.log4j.Logger.getLogger;
+import static org.apache.taverna.component.ui.annotation.SemanticAnnotationUtils.checkComponent;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.SaveException;
+
+import org.apache.log4j.Logger;
+import org.apache.taverna.component.api.Component;
+import org.apache.taverna.component.api.ComponentException;
+import org.apache.taverna.component.api.ComponentFactory;
+import org.apache.taverna.component.api.Family;
+import org.apache.taverna.component.api.Registry;
+import org.apache.taverna.component.api.Version;
+import org.apache.taverna.component.api.profile.SemanticAnnotationProfile;
+import org.apache.taverna.component.ui.serviceprovider.ComponentServiceProvider;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.validation.ValidationReport;
+import uk.org.taverna.scufl2.validation.structural.StructuralValidator;
+
+/**
+ * @author alanrw
+ */
+public class ComponentSaver extends AbstractDataflowPersistenceHandler
+		implements DataflowPersistenceHandler {
+	private static final String UNSATISFIED_PROFILE_WARNING = "The component does not satisfy the profile.\n"
+			+ "See validation report.\nDo you still want to save?";
+	private static final Logger logger = getLogger(ComponentSaver.class);
+
+	private ComponentFactory factory;
+	private ComponentServiceProvider provider;
+	private FileType cft;
+
+	public void setComponentFactory(ComponentFactory factory) {
+		this.factory = factory;
+	}
+
+	public void setFileType(FileType fileType) {
+		this.cft = fileType;
+	}
+
+	public void setServiceProvider(ComponentServiceProvider provider) {
+		this.provider = provider;
+	}
+
+	@Override
+	public DataflowInfo saveDataflow(WorkflowBundle bundle, FileType fileType,
+			Object destination) throws SaveException {
+		if (!getSaveFileTypes().contains(fileType))
+			throw new IllegalArgumentException("Unsupported file type "
+					+ fileType);
+		if (!(destination instanceof Version.ID))
+			throw new IllegalArgumentException("Unsupported destination type "
+					+ destination.getClass().getName());
+
+		ValidationReport structuralValidity = new StructuralValidator()
+				.validate(bundle);
+		if (structuralValidity.detectedProblems())
+			throw new SaveException(
+					"Cannot save a structurally invalid workflow as a component",
+					structuralValidity.getException());
+
+		/*
+		 * Saving an invalid dataflow is OK. Validity check is done to get
+		 * predicted depth for output (if possible)
+		 */
+
+		Version.ID ident = (Version.ID) destination;
+
+		if (ident.getComponentVersion() == -1) {
+			Version.ID newIdent = new Version.Identifier(
+					ident.getRegistryBase(), ident.getFamilyName(),
+					ident.getComponentName(), 0);
+			return new DataflowInfo(cft, newIdent, bundle);
+		}
+
+		Family family;
+		try {
+			Registry registry = factory.getRegistry(ident.getRegistryBase());
+			family = registry.getComponentFamily(ident.getFamilyName());
+		} catch (ComponentException e) {
+			throw new SaveException("Unable to read component", e);
+		}
+
+		Version newVersion = null;
+		try {
+			List<SemanticAnnotationProfile> problemProfiles = new ArrayList<>(
+					checkComponent(bundle, family.getComponentProfile()));
+
+			if (!problemProfiles.isEmpty()) {
+				int answer = showConfirmDialog(null,
+						UNSATISFIED_PROFILE_WARNING, "Profile problem",
+						OK_CANCEL_OPTION);
+				if (answer != OK_OPTION)
+					throw new SaveException("Saving cancelled");
+			}
+
+			JTextArea descriptionArea = new JTextArea(10, 60);
+			descriptionArea.setLineWrap(true);
+			descriptionArea.setWrapStyleWord(true);
+			final JScrollPane descriptionScrollPane = new JScrollPane(
+					descriptionArea);
+			if (ident.getComponentVersion() == 0) {
+				int answer = showConfirmDialog(null, descriptionScrollPane,
+						"Component description", OK_CANCEL_OPTION);
+				if (answer != OK_OPTION)
+					throw new SaveException("Saving cancelled");
+				newVersion = family.createComponentBasedOn(
+						ident.getComponentName(), descriptionArea.getText(),
+						bundle);
+			} else {
+				Component component = family.getComponent(ident
+						.getComponentName());
+				int answer = showConfirmDialog(null, descriptionScrollPane,
+						"Version description", OK_CANCEL_OPTION);
+				if (answer != OK_OPTION)
+					throw new SaveException("Saving cancelled");
+				newVersion = component.addVersionBasedOn(bundle,
+						descriptionArea.getText());
+			}
+		} catch (ComponentException e) {
+			logger.error("Unable to save new version of component", e);
+			throw new SaveException("Unable to save new version of component",
+					e);
+		}
+
+		Version.ID newIdent = new Version.Identifier(ident.getRegistryBase(),
+				ident.getFamilyName(), ident.getComponentName(),
+				newVersion.getVersionNumber());
+		provider.refreshProvidedComponent(ident);
+		return new DataflowInfo(cft, newIdent, bundle);
+	}
+
+	@Override
+	public List<FileType> getSaveFileTypes() {
+		return Arrays.<FileType> asList(cft);
+	}
+
+	@Override
+	public List<Class<?>> getSaveDestinationTypes() {
+		return Arrays.<Class<?>> asList(Version.ID.class);
+	}
+
+	@Override
+	public boolean wouldOverwriteDataflow(WorkflowBundle dataflow,
+			FileType fileType, Object destination, DataflowInfo lastDataflowInfo) {
+		if (!getSaveFileTypes().contains(fileType))
+			throw new IllegalArgumentException("Unsupported file type "
+					+ fileType);
+		return false;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/FileManagerObserver.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/FileManagerObserver.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/FileManagerObserver.java
new file mode 100644
index 0000000..0490874
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/file/FileManagerObserver.java
@@ -0,0 +1,128 @@
+package org.apache.taverna.component.ui.file;
+
+import static java.awt.Color.WHITE;
+import static java.awt.Font.BOLD;
+import static javax.swing.SwingUtilities.invokeLater;
+import static javax.swing.SwingUtilities.isEventDispatchThread;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Insets;
+
+import javax.swing.border.Border;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.StartupSPI;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.events.FileManagerEvent;
+import net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphController;
+import net.sf.taverna.t2.workbench.views.graph.GraphViewComponent;
+
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.taverna.component.api.Version;
+import org.apache.taverna.component.ui.util.Utils;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+public class FileManagerObserver implements StartupSPI {
+	private static final Color COLOR = new Color(230, 147, 210);
+
+	private FileManager fileManager;
+	private ColourManager colours;
+	private GraphViewComponent graphView;
+	private Utils utils;
+
+	public void setFileManager(FileManager fileManager) {
+		this.fileManager = fileManager;
+	}
+
+	public void setColourManager(ColourManager colours) {
+		this.colours = colours;
+	}
+
+	public void setGraphView(GraphViewComponent graphView) {
+		this.graphView = graphView;
+	}
+
+	public void setUtils(Utils utils) {
+		this.utils = utils;
+	}
+
+	@Override
+	public boolean startup() {
+		colours.setPreferredColour(
+				"org.apache.taverna.component.registry.Component", COLOR);
+		colours.setPreferredColour(
+				"org.apache.taverna.component.ComponentActivity", COLOR);
+		fileManager.addObserver(new Observer<FileManagerEvent>() {
+			@Override
+			public void notify(Observable<FileManagerEvent> observable,
+					FileManagerEvent event) throws Exception {
+				FileManagerObserverRunnable runnable = new FileManagerObserverRunnable();
+				if (isEventDispatchThread())
+					runnable.run();
+				else
+					invokeLater(runnable);
+			}
+		});
+		return true;
+	}
+
+	@Override
+	public int positionHint() {
+		return 200;
+	}
+
+	public class FileManagerObserverRunnable implements Runnable {
+		@Override
+		public void run() {
+			WorkflowBundle currentDataflow = fileManager.getCurrentDataflow();
+			if (currentDataflow == null)
+				return;
+			SVGGraphController graphController = (SVGGraphController) graphView
+					.getGraphController(currentDataflow.getMainWorkflow());
+			if (graphController == null)
+				return;
+			JSVGCanvas svgCanvas = graphController.getSVGCanvas();
+			Object dataflowSource = fileManager
+					.getDataflowSource(currentDataflow);
+			if (utils.currentDataflowIsComponent())
+				svgCanvas.setBorder(new ComponentBorder(
+						(Version.ID) dataflowSource));
+			else
+				svgCanvas.setBorder(null);
+			svgCanvas.repaint();
+		}
+	}
+
+	static class ComponentBorder implements Border {
+		private final Insets insets = new Insets(25, 0, 0, 0);
+		private final String text;
+
+		public ComponentBorder(Version.ID identification) {
+			text = "Component : " + identification.getComponentName();
+		}
+
+		@Override
+		public Insets getBorderInsets(java.awt.Component c) {
+			return insets;
+		}
+
+		@Override
+		public boolean isBorderOpaque() {
+			return true;
+		}
+
+		@Override
+		public void paintBorder(java.awt.Component c, Graphics g, int x, int y,
+				int width, int height) {
+			g.setColor(COLOR);
+			g.fillRect(x, y, width, 20);
+			g.setFont(g.getFont().deriveFont(BOLD));
+			g.setColor(WHITE);
+			g.drawString(text, x + 5, y + 15);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/localworld/LocalWorld.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/localworld/LocalWorld.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/localworld/LocalWorld.java
new file mode 100644
index 0000000..42bd492
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/localworld/LocalWorld.java
@@ -0,0 +1,92 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.localworld;
+
+import static com.hp.hpl.jena.rdf.model.ModelFactory.createOntologyModel;
+import static org.apache.log4j.Logger.getLogger;
+import static org.apache.taverna.component.ui.annotation.SemanticAnnotationUtils.createTurtle;
+import static org.apache.taverna.component.ui.annotation.SemanticAnnotationUtils.populateModelFromString;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.configuration.app.ApplicationConfiguration;
+
+import com.hp.hpl.jena.ontology.Individual;
+import com.hp.hpl.jena.ontology.OntClass;
+import com.hp.hpl.jena.ontology.OntModel;
+import com.hp.hpl.jena.rdf.model.Resource;
+
+/**
+ * @author alanrw
+ */
+public class LocalWorld {
+	private static final String FILENAME = "localWorld.ttl";
+	private static final Logger logger = getLogger(LocalWorld.class);
+	protected static final String ENCODING = "TURTLE";
+	private static LocalWorld instance = null;
+
+	private OntModel model;
+
+	public synchronized static LocalWorld getInstance() {
+		if (instance == null)
+			instance = new LocalWorld();
+		return instance;
+	}
+
+	private LocalWorld() {
+		File modelFile = new File(calculateComponentsDirectory(), FILENAME);
+		model = createOntologyModel();
+		if (modelFile.exists())
+			try (Reader in = new InputStreamReader(new FileInputStream(
+					modelFile), "UTF-8")) {
+				model.read(in, null, ENCODING);
+			} catch (IOException e) {
+				logger.error("failed to construct local annotation world", e);
+			}
+	}
+
+	ApplicationConfiguration config;//FIXME beaninject
+
+	public File calculateComponentsDirectory() {
+		return new File(config.getApplicationHomeDir(), "components");
+	}
+
+	public Individual createIndividual(String urlString, OntClass rangeClass) {
+		try {
+			return model.createIndividual(urlString, rangeClass);
+		} finally {
+			saveModel();
+		}
+	}
+
+	private void saveModel() {
+		File modelFile = new File(calculateComponentsDirectory(), FILENAME);
+		try (OutputStream out = new FileOutputStream(modelFile)) {
+			out.write(createTurtle(model).getBytes("UTF-8"));
+		} catch (IOException e) {
+			logger.error("failed to save local annotation world", e);
+		}
+	}
+
+	public List<Individual> getIndividualsOfClass(Resource clazz) {
+		return model.listIndividuals(clazz).toList();
+	}
+
+	public void addModelFromString(String addedModel) {
+		try {
+			populateModelFromString(model, addedModel);
+		} finally {
+			saveModel();
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/AbstractContextComponentMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/AbstractContextComponentMenuAction.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/AbstractContextComponentMenuAction.java
new file mode 100644
index 0000000..8a90895
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/AbstractContextComponentMenuAction.java
@@ -0,0 +1,39 @@
+package org.apache.taverna.component.ui.menu;
+
+import java.net.URI;
+
+import org.apache.taverna.component.api.config.ComponentConfig;
+
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.core.Processor;
+
+public abstract class AbstractContextComponentMenuAction extends AbstractContextualMenuAction {
+	public AbstractContextComponentMenuAction(URI parentId, int positionHint) {
+		super(parentId, positionHint);
+	}
+
+	public AbstractContextComponentMenuAction(URI parentId, int positionHint, URI id) {
+		super(parentId, positionHint, id);
+	}
+
+	protected boolean isComponentActivity(Activity act) {
+		if (act == null)
+			return false;
+		return act.getType().equals(ComponentConfig.URI);
+	}
+
+	protected Activity findActivity() {
+		if (getContextualSelection() == null)
+			return null;
+		Object selection = getContextualSelection().getSelection();
+		if (selection instanceof Processor) {
+			Processor processor = (Processor) selection;
+			return processor.getParent().getParent().getMainProfile()
+					.getProcessorBindings().getByName(processor.getName())
+					.getBoundActivity();
+		} else if (selection instanceof Activity)
+			return (Activity) selection;
+		return null;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentConfigureMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentConfigureMenuAction.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentConfigureMenuAction.java
new file mode 100644
index 0000000..effba35
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentConfigureMenuAction.java
@@ -0,0 +1,63 @@
+package org.apache.taverna.component.ui.menu;
+
+import static javax.swing.Action.NAME;
+import static org.apache.taverna.component.ui.ComponentConstants.ACTIVITY_URI;
+
+import javax.swing.Action;
+
+import org.apache.taverna.component.api.ComponentFactory;
+import org.apache.taverna.component.ui.config.ComponentConfigureAction;
+
+import uk.org.taverna.commons.services.ServiceRegistry;
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.activitytools.AbstractConfigureActivityMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+
+public class ComponentConfigureMenuAction extends
+		AbstractConfigureActivityMenuAction {
+	public ComponentConfigureMenuAction() {
+		super(ACTIVITY_URI);
+	}
+
+	private ActivityIconManager aim;
+	private ServiceDescriptionRegistry sdr;
+	private EditManager em;
+	private FileManager fm;
+	private ServiceRegistry str;
+	private ComponentFactory factory;
+
+	public void setActivityIconManager(ActivityIconManager aim) {
+		this.aim = aim;
+	}
+
+	public void setServiceDescriptionRegistry(ServiceDescriptionRegistry sdr) {
+		this.sdr = sdr;
+	}
+
+	public void setEditManager(EditManager em) {
+		this.em = em;
+	}
+
+	public void setFileManager(FileManager fm) {
+		this.fm = fm;
+	}
+
+	public void setServiceTypeRegistry(ServiceRegistry str) {
+		this.str = str;
+	}
+
+	public void setComponentFactory(ComponentFactory factory) {
+		this.factory = factory;
+	}
+
+	@Override
+	protected Action createAction() {
+		Action result = new ComponentConfigureAction(findActivity(),
+				getParentFrame(), factory, aim, sdr, em, fm, str);
+		result.putValue(NAME, "Configure component");
+		addMenuDots(result);
+		return result;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentMenu.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentMenu.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentMenu.java
new file mode 100644
index 0000000..f9fa643
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentMenu.java
@@ -0,0 +1,27 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.menu;
+
+import static net.sf.taverna.t2.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenu;
+
+/**
+ * @author alanrw
+ */
+public class ComponentMenu extends AbstractMenu {
+	public static final URI COMPONENT = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#component");
+	public static final String TITLE = "Components";
+
+	public ComponentMenu() {
+		super(DEFAULT_MENU_BAR, 950, COMPONENT, makeAction());
+	}
+
+	public static DummyAction makeAction() {
+		return new DummyAction(TITLE);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentSection.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentSection.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentSection.java
new file mode 100644
index 0000000..efeadb0
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/ComponentSection.java
@@ -0,0 +1,29 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class ComponentSection extends AbstractMenuSection {
+	public static final String COMPONENT_SECTION = "Components";
+	public static final URI componentSection = URI
+			.create("http://taverna.sf.net/2009/contextMenu/components");
+	public static final URI editSection = URI
+			.create("http://taverna.sf.net/2009/contextMenu/edit");
+
+	public ComponentSection() {
+		super(editSection, 100, componentSection);
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return super.isEnabled();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-plugin-component/blob/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/NestedWorkflowCreationDialog.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/NestedWorkflowCreationDialog.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/NestedWorkflowCreationDialog.java
new file mode 100644
index 0000000..0aecacf
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/NestedWorkflowCreationDialog.java
@@ -0,0 +1,628 @@
+/**
+ * 
+ */
+package org.apache.taverna.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 org.apache.taverna.component.ui.util.Utils.uniqueName;
+import static uk.org.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 uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.annotation.Annotation;
+import uk.org.taverna.scufl2.api.common.Named;
+import uk.org.taverna.scufl2.api.common.NamedSet;
+import uk.org.taverna.scufl2.api.common.Scufl2Tools;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.BlockingControlLink;
+import uk.org.taverna.scufl2.api.core.ControlLink;
+import uk.org.taverna.scufl2.api.core.DataLink;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.port.InputActivityPort;
+import uk.org.taverna.scufl2.api.port.InputProcessorPort;
+import uk.org.taverna.scufl2.api.port.InputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.OutputActivityPort;
+import uk.org.taverna.scufl2.api.port.OutputProcessorPort;
+import uk.org.taverna.scufl2.api.port.OutputWorkflowPort;
+import uk.org.taverna.scufl2.api.port.ProcessorPort;
+import uk.org.taverna.scufl2.api.port.ReceiverPort;
+import uk.org.taverna.scufl2.api.port.SenderPort;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * @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/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/NestedWorkflowCreatorMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/NestedWorkflowCreatorMenuAction.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/NestedWorkflowCreatorMenuAction.java
new file mode 100644
index 0000000..79f290d
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/NestedWorkflowCreatorMenuAction.java
@@ -0,0 +1,78 @@
+/**
+ * 
+ */
+package org.apache.taverna.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 net.sf.taverna.t2.activities.dataflow.servicedescriptions.DataflowActivityIcon;
+import net.sf.taverna.t2.ui.menu.AbstractContextualMenuAction;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.views.graph.GraphViewComponent;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @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/43334c1d/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/OpenComponentFromComponentActivityAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/OpenComponentFromComponentActivityAction.java b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/OpenComponentFromComponentActivityAction.java
new file mode 100644
index 0000000..2803f04
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/org/apache/taverna/component/ui/menu/OpenComponentFromComponentActivityAction.java
@@ -0,0 +1,66 @@
+/**
+ * 
+ */
+package org.apache.taverna.component.ui.menu;
+
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.event.ActionEvent;
+import java.net.MalformedURLException;
+
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import net.sf.taverna.t2.workbench.views.graph.GraphViewComponent;
+
+import org.apache.log4j.Logger;
+import org.apache.taverna.component.api.ComponentFactory;
+import org.apache.taverna.component.api.Version;
+import org.apache.taverna.component.ui.ComponentAction;
+import org.apache.taverna.component.ui.ComponentActivityConfigurationBean;
+import org.apache.taverna.component.ui.serviceprovider.ComponentServiceIcon;
+
+import uk.org.taverna.scufl2.api.activity.Activity;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @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;
+	}
+}