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:13 UTC

[26/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/annotation/SemanticAnnotationPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/SemanticAnnotationPanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/SemanticAnnotationPanel.java
new file mode 100644
index 0000000..536a87d
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/SemanticAnnotationPanel.java
@@ -0,0 +1,271 @@
+/*
+* 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.annotation;
+
+import static java.awt.BorderLayout.CENTER;
+import static java.awt.BorderLayout.NORTH;
+import static java.awt.Color.WHITE;
+import static java.awt.Font.BOLD;
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.EAST;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.SOUTHEAST;
+import static java.lang.Integer.MIN_VALUE;
+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.showConfirmDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static io.github.taverna_extras.component.ui.annotation.SemanticAnnotationUtils.getDisplayName;
+import static io.github.taverna_extras.component.ui.annotation.SemanticAnnotationUtils.getObjectName;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.border.EmptyBorder;
+
+import io.github.taverna_extras.component.api.profile.SemanticAnnotationProfile;
+
+import org.apache.jena.ontology.OntProperty;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Statement;
+import org.apache.taverna.lang.ui.DeselectingButton;
+
+public class SemanticAnnotationPanel extends JPanel {
+	private static final long serialVersionUID = -5949183295606132775L;
+
+	private List<PropertyPanelFactorySPI> propertyPanelFactories; //FIXME beaninject
+	private final AbstractSemanticAnnotationContextualView semanticAnnotationContextualView;
+	private final SemanticAnnotationProfile semanticAnnotationProfile;
+	private final Set<Statement> statements;
+	private final boolean allowChange;
+	private final PropertyPanelFactorySPI bestFactory;
+
+	public SemanticAnnotationPanel(
+			AbstractSemanticAnnotationContextualView semanticAnnotationContextualView,
+			SemanticAnnotationProfile semanticAnnotationProfile,
+			Set<Statement> statements, boolean allowChange) {
+		this.semanticAnnotationContextualView = semanticAnnotationContextualView;
+		this.semanticAnnotationProfile = semanticAnnotationProfile;
+		this.statements = statements;
+		this.allowChange = allowChange;
+		this.bestFactory = findBestPanelFactory();
+		initialise();
+	}
+
+	private void initialise() {
+		setLayout(new GridBagLayout());
+		// setBorder(new AbstractBorder() {
+		// @Override
+		// public void paintBorder(Component c, Graphics g, int x, int y, int
+		// width, int height) {
+		// g.setColor(Color.GRAY);
+		// g.drawLine(x, y+height-1, x+width-1, y+height-1);
+		// }
+		// });
+
+		GridBagConstraints c = new GridBagConstraints();
+		c.anchor = SOUTHEAST;
+		c.fill = BOTH;
+		c.weightx = 1;
+		c.gridx = 0;
+
+		OntProperty predicate = semanticAnnotationProfile.getPredicate();
+		c.gridwidth = 3;
+		JLabel label = new JLabel(format("Annotation type : %s",
+				getDisplayName(predicate)));
+		label.setBorder(new EmptyBorder(5, 5, 5, 5));
+		label.setBackground(WHITE);
+		label.setOpaque(true);
+		add(label, c);
+
+		c.insets = new Insets(7, 0, 0, 0);
+		c.anchor = EAST;
+		c.fill = HORIZONTAL;
+		if (statements.isEmpty()) {
+			c.gridwidth = 2;
+			// c.weightx = 1;
+			// c.gridy++;
+			add(new JLabel("No semantic annotations"), c);
+		} else {
+			c.gridwidth = 1;
+			for (Statement statement : statements) {
+				c.gridx = 0;
+				c.weightx = 1;
+				if (bestFactory != null) {
+					add(bestFactory.getDisplayComponent(
+							semanticAnnotationProfile, statement), c);
+				} else {
+					JTextArea value = new JTextArea(getObjectName(statement));
+					value.setLineWrap(true);
+					value.setWrapStyleWord(true);
+					value.setEditable(false);
+					value.setBackground(WHITE);
+					value.setOpaque(true);
+					value.setBorder(new EmptyBorder(2, 4, 2, 4));
+					add(value, c);
+				}
+				if (allowChange) {
+					c.gridx = 1;
+					c.weightx = 0;
+					add(createChangeButton(statement), c);
+
+					c.gridx = 2;
+					add(createDeleteButton(statement), c);
+				}
+			}
+		}
+
+		if (allowChange
+				&& !enoughAlready(statements,
+						semanticAnnotationProfile.getMaxOccurs())) {
+			c.gridx = 0;
+			c.gridwidth = 3;
+			c.anchor = SOUTHEAST;
+			c.fill = NONE;
+			add(createAddButton(), c);
+		}
+	}
+
+	private boolean enoughAlready(Set<Statement> statements, Integer maxOccurs) {
+		return (maxOccurs != null) && (statements.size() >= maxOccurs);
+	}
+
+	private JButton createChangeButton(final Statement statement) {
+		return new DeselectingButton("Change", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				addOrChangeAnnotation(statement);
+			}
+		});
+	}
+
+	private JButton createDeleteButton(final Statement statement) {
+		return new DeselectingButton("Delete", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				semanticAnnotationContextualView.removeStatement(statement);
+			}
+		});
+	}
+
+	private JButton createAddButton() {
+		return new DeselectingButton("Add Annotation", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				addOrChangeAnnotation(null);
+			}
+		});
+	}
+
+	private void addOrChangeAnnotation(Statement statement) {
+		JPanel annotationPanel = null;
+		JComponent inputComponent = null;
+
+		if (bestFactory != null) {
+			inputComponent = bestFactory.getInputComponent(
+					semanticAnnotationProfile, statement);
+			annotationPanel = getPropertyPanel(
+					getDisplayName(semanticAnnotationProfile.getPredicate()),
+					inputComponent);
+		}
+
+		if (annotationPanel == null) {
+			showMessageDialog(null, format("Unable to handle %s",
+					semanticAnnotationProfile.getPredicateString()),
+					"Annotation problem", ERROR_MESSAGE);
+			return;
+		}
+
+		int answer = showConfirmDialog(null, annotationPanel,
+				"Add/change annotation", OK_CANCEL_OPTION);
+		if (answer == OK_OPTION) {
+			RDFNode response = bestFactory.getNewTargetNode(statement,
+					inputComponent);
+			if (response == null)
+				return;
+			if (statement != null)
+				semanticAnnotationContextualView.changeStatement(statement,
+						semanticAnnotationProfile.getPredicate(), response);
+			else
+				semanticAnnotationContextualView.addStatement(
+						semanticAnnotationProfile.getPredicate(), response);
+		}
+	}
+
+	private PropertyPanelFactorySPI findBestPanelFactory() {
+		PropertyPanelFactorySPI result = null;
+		int currentRating = MIN_VALUE;
+		for (PropertyPanelFactorySPI factory : propertyPanelFactories) {
+			int ratingForSemanticAnnotation = factory
+					.getRatingForSemanticAnnotation(semanticAnnotationProfile);
+			if (ratingForSemanticAnnotation > currentRating) {
+				currentRating = ratingForSemanticAnnotation;
+				result = factory;
+			}
+		}
+		return result;
+	}
+
+	public static JPanel getPropertyPanel(String displayName,
+			Component inputComponent) {
+		JPanel result = new JPanel();
+		result.setLayout(new BorderLayout());
+		JPanel messagePanel = new JPanel(new BorderLayout());
+		messagePanel.setBorder(new EmptyBorder(5, 5, 0, 0));
+		messagePanel.setBackground(WHITE);
+		result.add(messagePanel, NORTH);
+
+		JLabel inputLabel = new JLabel("Enter a value for the annotation");
+		inputLabel.setBackground(WHITE);
+		Font baseFont = inputLabel.getFont();
+		inputLabel.setFont(baseFont.deriveFont(BOLD));
+		messagePanel.add(inputLabel, NORTH);
+
+		JTextArea messageText = new JTextArea(format(
+				"Enter a value for the annotation '%s'", displayName));
+		messageText.setMargin(new Insets(5, 10, 10, 10));
+		messageText.setMinimumSize(new Dimension(0, 30));
+		messageText.setFont(baseFont.deriveFont(11f));
+		messageText.setEditable(false);
+		messageText.setFocusable(false);
+		messagePanel.add(messageText, CENTER);
+
+		result.add(new JScrollPane(inputComponent), CENTER);
+		return result;
+	}
+}

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/annotation/SemanticAnnotationUtils.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/SemanticAnnotationUtils.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/SemanticAnnotationUtils.java
new file mode 100644
index 0000000..68050c0
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/SemanticAnnotationUtils.java
@@ -0,0 +1,190 @@
+/*
+* 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.annotation;
+
+import static org.apache.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 io.github.taverna_extras.component.api.ComponentException;
+import io.github.taverna_extras.component.api.profile.Profile;
+import io.github.taverna_extras.component.api.profile.SemanticAnnotationProfile;
+
+import org.apache.taverna.scufl2.api.annotation.Annotation;
+import org.apache.taverna.scufl2.api.common.AbstractNamed;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+
+import org.apache.jena.ontology.OntProperty;
+import org.apache.jena.ontology.OntResource;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/TurtleContextualView.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/TurtleContextualView.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/TurtleContextualView.java
new file mode 100644
index 0000000..8489fe6
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/TurtleContextualView.java
@@ -0,0 +1,93 @@
+/*
+* 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.annotation;
+
+import static java.awt.BorderLayout.CENTER;
+import static io.github.taverna_extras.component.ui.annotation.SemanticAnnotationUtils.findSemanticAnnotation;
+import static io.github.taverna_extras.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 org.slf4j.Logger;
+
+import org.apache.taverna.scufl2.api.annotation.Annotation;
+import org.apache.taverna.scufl2.api.common.AbstractNamed;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.workbench.ui.views.contextualviews.ContextualView;
+
+/**
+ * @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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/TurtleInputPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/TurtleInputPanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/TurtleInputPanel.java
new file mode 100644
index 0000000..7356cc9
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/TurtleInputPanel.java
@@ -0,0 +1,105 @@
+/*
+* 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.annotation;
+
+import static org.apache.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 io.github.taverna_extras.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 org.apache.jena.ontology.Individual;
+import org.apache.jena.ontology.OntClass;
+import org.apache.jena.ontology.OntModel;
+import org.apache.taverna.lang.ui.DeselectingButton;
+import org.apache.taverna.lang.ui.ReadOnlyTextArea;
+
+/**
+ * @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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/UnrecognizedStatementPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/UnrecognizedStatementPanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/UnrecognizedStatementPanel.java
new file mode 100644
index 0000000..67d806d
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/UnrecognizedStatementPanel.java
@@ -0,0 +1,43 @@
+/*
+* 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.annotation;
+
+import static java.lang.String.format;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.apache.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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/UnresolveablePredicatePanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/UnresolveablePredicatePanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/UnresolveablePredicatePanel.java
new file mode 100644
index 0000000..a7e2c89
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/annotation/UnresolveablePredicatePanel.java
@@ -0,0 +1,43 @@
+/*
+* 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.annotation;
+
+import static java.lang.String.format;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import io.github.taverna_extras.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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/config/ComponentConfigurationPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/config/ComponentConfigurationPanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/config/ComponentConfigurationPanel.java
new file mode 100644
index 0000000..712b861
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/config/ComponentConfigurationPanel.java
@@ -0,0 +1,171 @@
+/*
+* 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.config;
+
+import static java.awt.event.ItemEvent.SELECTED;
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.api.config.ComponentPropertyNames.COMPONENT_NAME;
+import static io.github.taverna_extras.component.api.config.ComponentPropertyNames.COMPONENT_VERSION;
+import static io.github.taverna_extras.component.api.config.ComponentPropertyNames.FAMILY_NAME;
+import static io.github.taverna_extras.component.api.config.ComponentPropertyNames.REGISTRY_BASE;
+import static io.github.taverna_extras.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 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.panel.ComponentListCellRenderer;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.services.ServiceRegistry;
+import org.apache.taverna.workbench.ui.views.contextualviews.activity.ActivityConfigurationPanel;
+
+@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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/config/ComponentConfigureAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/config/ComponentConfigureAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/config/ComponentConfigureAction.java
new file mode 100644
index 0000000..81c9d2f
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/config/ComponentConfigureAction.java
@@ -0,0 +1,69 @@
+/*
+* 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.config;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import io.github.taverna_extras.component.api.ComponentFactory;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.servicedescriptions.ServiceDescriptionRegistry;
+import org.apache.taverna.services.ServiceRegistry;
+import org.apache.taverna.workbench.activityicons.ActivityIconManager;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.ui.actions.activity.ActivityConfigurationAction;
+import org.apache.taverna.workbench.ui.views.contextualviews.activity.ActivityConfigurationDialog;
+
+@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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentDataflowHealthCheckExplainer.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentDataflowHealthCheckExplainer.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentDataflowHealthCheckExplainer.java
new file mode 100644
index 0000000..7a2442f
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentDataflowHealthCheckExplainer.java
@@ -0,0 +1,91 @@
+/*
+* 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.file;
+
+import static java.util.Collections.sort;
+import static io.github.taverna_extras.component.ui.annotation.SemanticAnnotationUtils.getDisplayName;
+import static io.github.taverna_extras.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 io.github.taverna_extras.component.api.profile.SemanticAnnotationProfile;
+import io.github.taverna_extras.component.ui.util.ComponentHealthCheck;
+import org.apache.taverna.visit.VisitKind;
+import org.apache.taverna.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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentDataflowHealthChecker.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentDataflowHealthChecker.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentDataflowHealthChecker.java
new file mode 100644
index 0000000..7ce0c1b
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentDataflowHealthChecker.java
@@ -0,0 +1,114 @@
+/*
+* 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.file;
+
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.ui.annotation.SemanticAnnotationUtils.checkComponent;
+import static io.github.taverna_extras.component.ui.util.ComponentHealthCheck.FAILS_PROFILE;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import io.github.taverna_extras.component.api.ComponentException;
+import io.github.taverna_extras.component.api.ComponentFactory;
+import io.github.taverna_extras.component.api.Family;
+import io.github.taverna_extras.component.api.Version;
+import io.github.taverna_extras.component.api.profile.SemanticAnnotationProfile;
+import io.github.taverna_extras.component.ui.util.ComponentHealthCheck;
+
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.visit.VisitReport;
+import static org.apache.taverna.visit.VisitReport.Status.SEVERE;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workflowmodel.Dataflow;
+import org.apache.taverna.workflowmodel.health.HealthChecker;
+
+/**
+ * @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;
+		}
+	}
+//
+//    @Override
+//    public VisitReport visit(Dataflow o, List<Object> ancestry) {
+//        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+//    }
+//
+//    @Override
+//    public boolean isTimeConsuming() {
+//        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+//    }
+}

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/file/ComponentOpener.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentOpener.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentOpener.java
new file mode 100644
index 0000000..f2e16e6
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentOpener.java
@@ -0,0 +1,88 @@
+/*
+* 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.file;
+
+import static org.apache.log4j.Logger.getLogger;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+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.api.Version.ID;
+
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.workbench.file.AbstractDataflowPersistenceHandler;
+import org.apache.taverna.workbench.file.DataflowInfo;
+import org.apache.taverna.workbench.file.DataflowPersistenceHandler;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.exceptions.OpenException;
+
+/**
+ * @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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentSaver.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentSaver.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentSaver.java
new file mode 100644
index 0000000..19b52d9
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/ComponentSaver.java
@@ -0,0 +1,185 @@
+/*
+* 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.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 io.github.taverna_extras.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 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.Family;
+import io.github.taverna_extras.component.api.Registry;
+import io.github.taverna_extras.component.api.Version;
+import io.github.taverna_extras.component.api.profile.SemanticAnnotationProfile;
+import io.github.taverna_extras.component.ui.serviceprovider.ComponentServiceProvider;
+
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.validation.ValidationReport;
+import org.apache.taverna.scufl2.validation.structural.StructuralValidator;
+import org.apache.taverna.workbench.file.AbstractDataflowPersistenceHandler;
+import org.apache.taverna.workbench.file.DataflowInfo;
+import org.apache.taverna.workbench.file.DataflowPersistenceHandler;
+import org.apache.taverna.workbench.file.FileType;
+import org.apache.taverna.workbench.file.exceptions.SaveException;
+
+/**
+ * @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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/FileManagerObserver.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/FileManagerObserver.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/FileManagerObserver.java
new file mode 100644
index 0000000..8ee99b9
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/file/FileManagerObserver.java
@@ -0,0 +1,146 @@
+/*
+* 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.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 org.apache.batik.swing.JSVGCanvas;
+import io.github.taverna_extras.component.api.Version;
+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.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.workbench.StartupSPI;
+import org.apache.taverna.workbench.configuration.colour.ColourManager;
+import org.apache.taverna.workbench.file.FileManager;
+import org.apache.taverna.workbench.file.events.FileManagerEvent;
+import org.apache.taverna.workbench.models.graph.svg.SVGGraphController;
+import org.apache.taverna.workbench.views.graph.GraphViewComponent;
+
+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(
+				"io.github.taverna_extras.component.registry.Component", COLOR);
+		colours.setPreferredColour(
+				"io.github.taverna_extras.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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/localworld/LocalWorld.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/localworld/LocalWorld.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/localworld/LocalWorld.java
new file mode 100644
index 0000000..a35ab61
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/localworld/LocalWorld.java
@@ -0,0 +1,107 @@
+/*
+* 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.localworld;
+
+import static org.apache.jena.rdf.model.ModelFactory.createOntologyModel;
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.ui.annotation.SemanticAnnotationUtils.createTurtle;
+import static io.github.taverna_extras.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 org.apache.jena.ontology.Individual;
+import org.apache.jena.ontology.OntClass;
+import org.apache.jena.ontology.OntModel;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * @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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/AbstractContextComponentMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/AbstractContextComponentMenuAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/AbstractContextComponentMenuAction.java
new file mode 100644
index 0000000..0911a66
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/AbstractContextComponentMenuAction.java
@@ -0,0 +1,58 @@
+/*
+* 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 io.github.taverna_extras.component.api.config.ComponentConfig;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.ui.menu.AbstractContextualMenuAction;
+
+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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentConfigureMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentConfigureMenuAction.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentConfigureMenuAction.java
new file mode 100644
index 0000000..0feb63e
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentConfigureMenuAction.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 javax.swing.Action.NAME;
+import static io.github.taverna_extras.component.ui.ComponentConstants.ACTIVITY_URI;
+
+import javax.swing.Action;
+
+import io.github.taverna_extras.component.api.ComponentFactory;
+import io.github.taverna_extras.component.ui.config.ComponentConfigureAction;
+import org.apache.taverna.servicedescriptions.ServiceDescriptionRegistry;
+import org.apache.taverna.services.ServiceRegistry;
+import org.apache.taverna.workbench.activityicons.ActivityIconManager;
+import org.apache.taverna.workbench.activitytools.AbstractConfigureActivityMenuAction;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentMenu.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentMenu.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentMenu.java
new file mode 100644
index 0000000..99cec65
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentMenu.java
@@ -0,0 +1,41 @@
+/*
+* 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 org.apache.taverna.ui.menu.AbstractMenu;
+import static org.apache.taverna.ui.menu.DefaultMenuBar.DEFAULT_MENU_BAR;
+
+/**
+ * @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/b7b61e71/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentSection.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentSection.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentSection.java
new file mode 100644
index 0000000..337ee73
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/menu/ComponentSection.java
@@ -0,0 +1,44 @@
+/*
+* 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 org.apache.taverna.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();
+	}
+}