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

[22/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/panel/RegistryChoiceMessage.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/RegistryChoiceMessage.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/RegistryChoiceMessage.java
new file mode 100644
index 0000000..85249ca
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/RegistryChoiceMessage.java
@@ -0,0 +1,40 @@
+/*
+* 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.panel;
+
+import io.github.taverna_extras.component.api.Registry;
+
+/**
+ * @author alanrw
+ */
+public class RegistryChoiceMessage {
+	private final Registry chosenRegistry;
+
+	public RegistryChoiceMessage(Registry chosenRegistry) {
+		this.chosenRegistry = chosenRegistry;
+	}
+
+	/**
+	 * @return the chosenRegistry
+	 */
+	public Registry getChosenRegistry() {
+		return chosenRegistry;
+	}
+}

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/panel/RegistryChooserPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/RegistryChooserPanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/RegistryChooserPanel.java
new file mode 100644
index 0000000..1e2bf78
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/RegistryChooserPanel.java
@@ -0,0 +1,138 @@
+/*
+* 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.panel;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.WEST;
+import static java.awt.event.ItemEvent.SELECTED;
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.ui.util.Utils.LONG_STRING;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.Vector;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.apache.log4j.Logger;
+import io.github.taverna_extras.component.api.Registry;
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+import org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+
+/**
+ * @author alanrw
+ */
+public class RegistryChooserPanel extends JPanel implements
+		Observable<RegistryChoiceMessage> {
+	private static final String REGISTRY_LABEL = "Component registry:";
+	private static final long serialVersionUID = 8390860727800654604L;
+	private static final Logger logger = getLogger(RegistryChooserPanel.class);
+
+	private final List<Observer<RegistryChoiceMessage>> observers = new ArrayList<>();
+	private final JComboBox<String> registryBox;
+	private final SortedMap<String, Registry> registryMap;
+
+	public RegistryChooserPanel(ComponentPreference pref) {
+		setLayout(new GridBagLayout());
+
+		GridBagConstraints gbc = new GridBagConstraints();
+
+		registryMap = pref.getRegistryMap();
+		registryBox = new JComboBox<>(new Vector<>(registryMap.keySet()));
+		registryBox.setPrototypeDisplayValue(LONG_STRING);
+		registryBox.setEditable(false);
+
+		gbc.gridx = 0;
+		gbc.anchor = WEST;
+		this.add(new JLabel(REGISTRY_LABEL), gbc);
+		gbc.gridx = 1;
+		gbc.weightx = 1;
+		gbc.fill = BOTH;
+		this.add(registryBox, gbc);
+
+		registryBox.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent event) {
+				if (event.getStateChange() == SELECTED)
+					dealWithSelection();
+			}
+		});
+
+		String firstKey = registryMap.firstKey();
+		registryBox.setSelectedItem(firstKey);
+		dealWithSelection();
+	}
+
+	private void updateToolTipText() {
+		String key = (String) registryBox.getSelectedItem();
+		Registry registry = registryMap.get(key);
+		registryBox.setToolTipText(registry.getRegistryBase().toString());
+	}
+
+	private void dealWithSelection() {
+		updateToolTipText();
+		Registry chosenRegistry = getChosenRegistry();
+		RegistryChoiceMessage message = new RegistryChoiceMessage(
+				chosenRegistry);
+		for (Observer<RegistryChoiceMessage> o : getObservers())
+			try {
+				o.notify(this, message);
+			} catch (Exception e) {
+				logger.error("problem handling selection update", e);
+			}
+	}
+
+	@Override
+	public void addObserver(Observer<RegistryChoiceMessage> observer) {
+		observers.add(observer);
+		Registry chosenRegistry = getChosenRegistry();
+		RegistryChoiceMessage message = new RegistryChoiceMessage(
+				chosenRegistry);
+		try {
+			observer.notify(this, message);
+		} catch (Exception e) {
+			logger.error("problem handling addition of observer", e);
+		}
+	}
+
+	@Override
+	public List<Observer<RegistryChoiceMessage>> getObservers() {
+		return observers;
+	}
+
+	@Override
+	public void removeObserver(Observer<RegistryChoiceMessage> observer) {
+		observers.remove(observer);
+	}
+
+	public Registry getChosenRegistry() {
+		if (registryBox.getSelectedIndex() < 0)
+			return null;
+		return registryMap.get(registryBox.getSelectedItem());
+	}
+}

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/panel/SearchChoicePanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/SearchChoicePanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/SearchChoicePanel.java
new file mode 100644
index 0000000..f26cf6b
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/SearchChoicePanel.java
@@ -0,0 +1,259 @@
+/*
+* 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.panel;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.WEST;
+import static java.awt.event.ItemEvent.SELECTED;
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.ui.util.Utils.LONG_STRING;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ExecutionException;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingWorker;
+
+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.Registry;
+import io.github.taverna_extras.component.api.Version;
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+
+/**
+ * @author alanrw
+ */
+@SuppressWarnings("serial")
+public class SearchChoicePanel extends JPanel {
+	private static final Logger logger = getLogger(SearchChoicePanel.class);
+	private static final String SEARCHING = "Searching...";
+	private static final String[] SEARCHING_ARRAY = new String[] { SEARCHING };
+	private static final String NO_MATCHES = "No matches";
+	private static final String SEARCH_FAILED = "Search failed";
+	private static final List<String> RESERVED_WORDS = Arrays.asList(SEARCHING,
+			NO_MATCHES, SEARCH_FAILED);
+
+	private ComponentPreference preference;//FIXME beaninject from constructor
+	private ComponentFactory factory;//FIXME beaninject from constructor
+	private Registry registry;
+	private String prefixes;
+	private String queryText;
+	private JLabel registryURLLabel;
+	private JComboBox<String> familyBox;
+	private JComboBox<String> componentBox;
+	private JComboBox<Object> versionBox;
+
+	public SearchChoicePanel(Registry registry, String prefixes,
+			String queryText) {
+		super(new GridBagLayout());
+		this.registry = registry;
+		this.prefixes = prefixes;
+		this.queryText = queryText;
+
+		componentBox = new JComboBox<>(SEARCHING_ARRAY);
+		componentBox.setPrototypeDisplayValue(LONG_STRING);
+		familyBox = new JComboBox<>(SEARCHING_ARRAY);
+		familyBox.setPrototypeDisplayValue(LONG_STRING);
+		versionBox = new JComboBox<Object>(SEARCHING_ARRAY);
+		versionBox.setPrototypeDisplayValue(LONG_STRING);
+
+		GridBagConstraints gbc = new GridBagConstraints();
+
+		JLabel registryLabel = new JLabel("Component registry:");
+
+		gbc.insets.left = 5;
+		gbc.insets.right = 5;
+		gbc.gridx = 0;
+		gbc.anchor = WEST;
+		gbc.fill = BOTH;
+		gbc.gridwidth = 1;
+		gbc.weightx = 1;
+		gbc.gridy++;
+		this.add(registryLabel, gbc);
+		gbc.gridx = 1;
+		registryURLLabel = new JLabel(SEARCHING);
+		this.add(registryURLLabel, gbc);
+		gbc.gridx = 0;
+		gbc.gridy++;
+		this.add(new JLabel("Component family:"), gbc);
+		gbc.gridx = 1;
+
+		this.add(familyBox, gbc);
+		gbc.gridx = 0;
+		gbc.gridy++;
+		this.add(new JLabel("Component:"), gbc);
+		gbc.gridx = 1;
+		this.add(componentBox, gbc);
+
+		gbc.gridx = 0;
+		gbc.gridy++;
+
+		this.add(new JLabel("Component version:"), gbc);
+		gbc.gridx = 1;
+		this.add(versionBox, gbc);
+
+		new Searcher().execute();
+	}
+
+	private class Searcher extends SwingWorker<Set<Version.ID>, Object> {
+		@Override
+		protected Set<Version.ID> doInBackground() throws Exception {
+			return registry.searchForComponents(prefixes, queryText);
+		}
+
+		@Override
+		protected void done() {
+			clearAll();
+			try {
+				Set<Version.ID> matches = get();
+				if (matches.isEmpty())
+					setAll(NO_MATCHES);
+				else
+					searchCompletedSuccessfully(matches);
+			} catch (InterruptedException e) {
+				logger.error("search was interrupted", e);
+				setAll(SEARCH_FAILED);
+			} catch (ExecutionException e) {
+				logger.error("problem in execution", e.getCause());
+				setAll(SEARCH_FAILED);
+			}
+		}
+	}
+
+	private void clearAll() {
+		familyBox.removeAllItems();
+		componentBox.removeAllItems();
+		versionBox.removeAllItems();
+	}
+
+	private void setAll(String text) {
+		registryURLLabel.setText(text);
+		familyBox.addItem(text);
+		componentBox.addItem(text);
+		versionBox.addItem(text);
+	}
+
+	private String[] calculateMatchingFamilyNames(
+			Set<Version.ID> matchingComponents) {
+		Set<String> result = new TreeSet<>();
+		for (Version.ID v : matchingComponents)
+			result.add(v.getFamilyName());
+		return result.toArray(new String[0]);
+	}
+
+	private void updateComponentBox(Set<Version.ID> matchingComponents,
+			JComboBox<String> componentBox, String selectedItem) {
+		componentBox.removeAllItems();
+		String[] matchingComponentNames = calculateMatchingComponentNames(
+				matchingComponents, selectedItem);
+		for (String componentName : matchingComponentNames)
+			componentBox.addItem(componentName);
+		componentBox.setSelectedIndex(0);
+	}
+
+	private String[] calculateMatchingComponentNames(
+			Set<Version.ID> matchingComponents, String familyName) {
+		Set<String> result = new TreeSet<>();
+		for (Version.ID v : matchingComponents)
+			if (v.getFamilyName().equals(familyName))
+				result.add(v.getComponentName());
+		return result.toArray(new String[0]);
+	}
+
+	private void updateVersionBox(Set<Version.ID> matchingComponents,
+			JComboBox<Object> versionBox, String componentName,
+			String familyName) {
+		versionBox.removeAllItems();
+		for (Integer v : calculateMatchingVersionNumbers(matchingComponents,
+				componentName, familyName))
+			versionBox.addItem(v);
+		versionBox.setSelectedIndex(0);
+	}
+
+	private Integer[] calculateMatchingVersionNumbers(
+			Set<Version.ID> matchingComponents, String componentName,
+			String familyName) {
+		Set<Integer> result = new TreeSet<>();
+		for (Version.ID v : matchingComponents)
+			if (v.getFamilyName().equals(familyName)
+					&& v.getComponentName().equals(componentName))
+				result.add(v.getComponentVersion());
+		return result.toArray(new Integer[0]);
+	}
+
+	public Version.ID getVersionIdentification() {
+		String registryString = registryURLLabel.getText();
+		if (RESERVED_WORDS.contains(registryString))
+			return null;
+
+		try {
+			return factory.getVersion(registry.getRegistryBase(),
+					(String) familyBox.getSelectedItem(),
+					(String) componentBox.getSelectedItem(),
+					(Integer) versionBox.getSelectedItem()).getID();
+		} catch (ComponentException e) {
+			logger.warn(
+					"unexpected failure to construct component version token",
+					e);
+			return null;
+		}
+	}
+
+	private void searchCompletedSuccessfully(final Set<Version.ID> matches) {
+		Version.ID one = (Version.ID) matches.toArray()[0];
+		registryURLLabel.setText(preference.getRegistryName(one
+				.getRegistryBase()));
+		for (String familyName : calculateMatchingFamilyNames(matches))
+			familyBox.addItem(familyName);
+		familyBox.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent e) {
+				if (e.getStateChange() == SELECTED)
+					updateComponentBox(matches, componentBox,
+							(String) familyBox.getSelectedItem());
+			}
+		});
+		componentBox.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent e) {
+				if (e.getStateChange() == SELECTED)
+					updateVersionBox(matches, versionBox,
+							(String) componentBox.getSelectedItem(),
+							(String) familyBox.getSelectedItem());
+			}
+		});
+		familyBox.setSelectedIndex(0);
+		updateComponentBox(matches, componentBox,
+				(String) familyBox.getSelectedItem());
+		updateVersionBox(matches, versionBox,
+				(String) componentBox.getSelectedItem(),
+				(String) familyBox.getSelectedItem());
+	}
+}

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/panel/SharingPolicyChooserPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/SharingPolicyChooserPanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/SharingPolicyChooserPanel.java
new file mode 100644
index 0000000..3625a84
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/panel/SharingPolicyChooserPanel.java
@@ -0,0 +1,157 @@
+/*
+* 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.panel;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.ui.util.Utils.LONG_STRING;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
+
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingWorker;
+
+import org.apache.log4j.Logger;
+import io.github.taverna_extras.component.api.ComponentException;
+import io.github.taverna_extras.component.api.Registry;
+import io.github.taverna_extras.component.api.SharingPolicy;
+import org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+
+/**
+ * @author alanrw
+ */
+public class SharingPolicyChooserPanel extends JPanel {
+	private static final String SHARING_LABEL = "Sharing policy:";
+	private static final String READING_MSG = "Reading sharing policies";
+	private static final String NO_PERMISSIONS_MSG = "No permissions available";
+	private static final long serialVersionUID = 2175274929391537032L;
+	private static final Logger logger = getLogger(SharingPolicyChooserPanel.class);
+
+	private final JComboBox<String> permissionBox = new JComboBox<>();
+	private final SortedMap<String, SharingPolicy> permissionMap = new TreeMap<>();
+	private Registry registry;
+
+	public SharingPolicyChooserPanel(RegistryChooserPanel registryPanel) {
+		this();
+		registryPanel.addObserver(new Observer<RegistryChoiceMessage>(){
+			@Override
+			public void notify(Observable<RegistryChoiceMessage> sender,
+					RegistryChoiceMessage message) throws Exception {
+				try {
+					registry = message.getChosenRegistry();
+					updateProfileModel();
+				} catch (Exception e) {
+					logger.error("problem when handling notification of registry", e);
+				}
+			}
+		});
+	}
+	public SharingPolicyChooserPanel() {
+		super();
+		permissionBox.setPrototypeDisplayValue(LONG_STRING);
+		this.setLayout(new GridBagLayout());
+
+		GridBagConstraints gbc = new GridBagConstraints();
+
+		gbc.gridx = 0;
+		gbc.gridy = 0;
+		gbc.anchor = WEST;
+		gbc.fill = NONE;
+		this.add(new JLabel(SHARING_LABEL), gbc);
+		gbc.gridx = 1;
+		gbc.weightx = 1;
+		gbc.fill = BOTH;
+		this.add(permissionBox, gbc);
+
+		permissionBox.setEditable(false);
+	}
+
+	private void updateProfileModel() {
+		permissionMap.clear();
+		permissionBox.removeAllItems();
+		permissionBox.addItem(READING_MSG);
+		permissionBox.setEnabled(false);
+		new SharingPolicyUpdater().execute();
+	}
+
+	public SharingPolicy getChosenPermission() {
+		if (permissionBox.getSelectedIndex() < 0)
+			return null;
+		return permissionMap.get(permissionBox.getSelectedItem());
+	}
+
+	private class SharingPolicyUpdater extends SwingWorker<String, Object> {
+		@Override
+		protected String doInBackground() throws Exception {
+			List<SharingPolicy> sharingPolicies;
+			if (registry == null)
+				return null;
+			try {
+				sharingPolicies = registry.getPermissions();
+				if (sharingPolicies == null)
+					return null;
+			} catch (ComponentException e) {
+				logger.error("problem getting permissions", e);
+				throw e;
+			} catch (NullPointerException e) {
+				logger.error("null pointer getting permissions", e);
+				throw e;
+			}
+
+			for (SharingPolicy policy : sharingPolicies)
+				try {
+					permissionMap.put(policy.getName(), policy);
+				} catch (NullPointerException e) {
+					logger.error("problem getting name of policy", e);
+				}
+			return null;
+		}
+
+		@Override
+		protected void done() {
+			permissionBox.removeAllItems();
+			try {
+				get();
+				for (String name : permissionMap.keySet())
+					permissionBox.addItem(name);
+				if (!permissionMap.isEmpty()) {
+					String firstKey = permissionMap.firstKey();
+					permissionBox.setSelectedItem(firstKey);
+				} else {
+					permissionBox.addItem(NO_PERMISSIONS_MSG);
+				}
+			} catch (InterruptedException | ExecutionException e) {
+				logger.error(e);
+				permissionBox.addItem("Unable to read permissions");
+			}
+			permissionBox.setEnabled(!permissionMap.isEmpty());
+		}
+	}
+}

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/preference/ComponentDefaults.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentDefaults.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentDefaults.java
new file mode 100644
index 0000000..faad899
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentDefaults.java
@@ -0,0 +1,57 @@
+/*
+* 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.preference;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.taverna.configuration.app.ApplicationConfiguration;
+
+/**
+ * Factored out defaults location system.
+ * 
+ * @author Donal Fellows
+ */
+public class ComponentDefaults {
+    public static final String REGISTRY_LIST = "REGISTRY_NAMES";
+    private static final String LOCAL_NAME = "local registry";
+    private static final String MYEXPERIMENT_NAME = "myExperiment";
+    private static final String MYEXPERIMENT_SITE = "http://www.myexperiment.org";
+    public static final String DEFAULT_REGISTRY_LIST = LOCAL_NAME + "," + MYEXPERIMENT_NAME;
+
+    public static Map<String, String> getDefaultProperties() {
+    	// Capacity = 3; we know that this is going to have 3 entries
+    	Map<String, String> defaults = new LinkedHashMap<>(3);
+    	defaults.put(LOCAL_NAME, calculateComponentsDirectoryPath());
+    	defaults.put(MYEXPERIMENT_NAME, MYEXPERIMENT_SITE);
+    	defaults.put(REGISTRY_LIST, DEFAULT_REGISTRY_LIST);
+    	return defaults;
+    }
+
+    static ApplicationConfiguration config;//FIXME beaninject (and beanify!)
+
+	public static String calculateComponentsDirectoryPath() {
+		return new File(config.getApplicationHomeDir(), "components").toURI()
+				.toASCIIString();
+	}
+
+	private ComponentDefaults() {
+	}
+}

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/preference/ComponentPreference.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreference.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreference.java
new file mode 100644
index 0000000..b7ff333
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreference.java
@@ -0,0 +1,143 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.preference;
+
+import static org.apache.commons.lang.StringUtils.join;
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.ui.preference.ComponentDefaults.REGISTRY_LIST;
+import static io.github.taverna_extras.component.ui.preference.ComponentDefaults.getDefaultProperties;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+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.Registry;
+import org.apache.taverna.configuration.AbstractConfigurable;
+import org.apache.taverna.configuration.ConfigurationManager;
+
+/**
+ * @author alanrw
+ */
+public class ComponentPreference extends AbstractConfigurable {
+	public static final String DISPLAY_NAME = "Components";
+	private final Logger logger = getLogger(ComponentPreference.class);
+
+	private SortedMap<String, Registry> registryMap = new TreeMap<>();
+	private ComponentFactory factory;
+
+	public ComponentPreference(ConfigurationManager cm, ComponentFactory factory) {
+		super(cm);
+		this.factory = factory;
+		updateRegistryMap();
+	}
+
+	private void updateRegistryMap() {
+		registryMap.clear();
+
+		for (String key : getRegistryKeys()) {
+			String value = super.getProperty(key);
+			try {
+				registryMap.put(key, factory.getRegistry(new URL(
+						value)));
+			} catch (MalformedURLException e) {
+				logger.error("bogus url (" + value
+						+ ") in configuration file", e);
+			} catch (ComponentException e) {
+				logger.error("failed to construct registry handle for "
+						+ value, e);
+			}
+		}
+	}
+	
+	private String[] getRegistryKeys() {
+		String registryNamesConcatenated = super.getProperty(REGISTRY_LIST);
+		if (registryNamesConcatenated == null)
+			return getDefaultPropertyMap().keySet().toArray(new String[]{});
+		return registryNamesConcatenated.split(",");
+	}
+
+	@Override
+	public String getFilePrefix() {
+		return "Component";
+	}
+
+	@Override
+	public String getUUID() {
+		return "2317A297-2AE0-42B5-86DC-99C9B7C0524A";
+	}
+
+	/**
+	 * @return the registryMap
+	 */
+	public SortedMap<String, Registry> getRegistryMap() {
+		return registryMap;
+	}
+
+	public String getRegistryName(URL registryBase) {
+		// Trim trailing '/' characters to ensure match.
+		String base = registryBase.toString();
+		while (base.endsWith("/"))
+			base = base.substring(0, base.length() - 1);
+
+		for (Entry<String, Registry> entry : registryMap.entrySet())
+			if (entry.getValue().getRegistryBaseString().equals(base))
+				return entry.getKey();
+		return base;
+	}
+
+	public void setRegistryMap(SortedMap<String, Registry> registries) {
+		registryMap.clear();
+		registryMap.putAll(registries);
+		super.clear();
+		List<String> keyList = new ArrayList<>();
+		for (Entry<String, Registry> entry : registryMap.entrySet()) {
+			String key = entry.getKey();
+			keyList.add(key);
+			super.setProperty(key, entry.getValue().getRegistryBaseString());
+		}
+		Collections.sort(keyList);
+		String registryNamesConcatenated = join(keyList, ",");
+		super.setProperty(REGISTRY_LIST, registryNamesConcatenated);
+	}
+
+	@Override
+	public Map<String, String> getDefaultPropertyMap() {
+		return getDefaultProperties();
+	}
+
+	@Override
+	public String getDisplayName() {
+		return DISPLAY_NAME;
+	}
+
+	@Override
+	public String getCategory() {
+		return "general";
+	}
+}

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/preference/ComponentPreferencePanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreferencePanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreferencePanel.java
new file mode 100644
index 0000000..4677c0c
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreferencePanel.java
@@ -0,0 +1,300 @@
+/*
+* 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.preference;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.CENTER;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.WEST;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static javax.swing.JTable.AUTO_RESIZE_LAST_COLUMN;
+import static javax.swing.ListSelectionModel.SINGLE_SELECTION;
+import static org.apache.log4j.Logger.getLogger;
+import static io.github.taverna_extras.component.ui.util.Utils.URL_PATTERN;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.awt.event.MouseEvent;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.swing.AbstractAction;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.border.EmptyBorder;
+
+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.Registry;
+import org.apache.taverna.lang.ui.DeselectingButton;
+import org.apache.taverna.lang.ui.ValidatingUserInputDialog;
+import org.apache.taverna.workbench.helper.Helper;
+
+public class ComponentPreferencePanel extends JPanel {
+	private static final String BAD_URL_MESSAGE = "Invalid URL";
+	private static final String SET_URL_MESSAGE = "Set the URL of the profile";
+	private static final String HELP_LABEL = "Help";
+	private static final String RESET_LABEL = "Reset";
+	private static final String APPLY_LABEL = "Apply";
+	private static final String ADD_REMOTE_TITLE = "Add Remote Component Registry";
+	private static final String ADD_LOCAL_TITLE = "Add Local Component Registry";
+	private static final String ADD_REMOTE_LABEL = "Add remote registry";
+	private static final String ADD_LOCAL_LABEL = "Add local registry";
+	private static final String REMOVE_LABEL = "Remove registry";
+	private static final String TITLE = "Component registry management";
+	private static final String VALIDATION_MESSAGE = "Set the registry name";
+	private static final String EXCEPTION_MESSAGE = "Unable to access registry at ";
+	private static final String EXCEPTION_TITLE = "Component registry problem";
+	private static final String INVALID_NAME = "Invalid registry name";
+	private static final String DUPLICATE = "Duplicate registry name";
+	private static final long serialVersionUID = 1310173658718093383L;
+
+	private final Logger logger = getLogger(ComponentPreferencePanel.class);
+
+	private ComponentFactory factory;
+	private ComponentPreference prefs;
+	private RegistryTableModel tableModel = new RegistryTableModel();
+
+	@SuppressWarnings("serial")
+	private JTable registryTable = new JTable(tableModel) {
+		@Override
+		public String getToolTipText(MouseEvent me) {
+			int row = rowAtPoint(me.getPoint());
+			if (row >= 0)
+				return tableModel.getRowTooltipText(row);
+			return super.getToolTipText(me);
+		}
+	};
+
+	public ComponentPreferencePanel(ComponentFactory componentFactory,
+			ComponentPreference preferences) {
+		super(new GridBagLayout());
+		factory = componentFactory;
+		prefs = preferences;
+
+		GridBagConstraints gbc = new GridBagConstraints();
+
+		// Title describing what kind of settings we are configuring here
+		JTextArea descriptionText = new JTextArea(TITLE);
+		descriptionText.setLineWrap(true);
+		descriptionText.setWrapStyleWord(true);
+		descriptionText.setEditable(false);
+		descriptionText.setFocusable(false);
+		descriptionText.setBorder(new EmptyBorder(10, 10, 10, 10));
+		gbc.anchor = WEST;
+		gbc.gridx = 0;
+		gbc.gridy = 0;
+		gbc.gridwidth = 1;
+		gbc.weightx = 1.0;
+		gbc.weighty = 0.0;
+		gbc.fill = HORIZONTAL;
+		add(descriptionText, gbc);
+
+		gbc.gridy++;
+		gbc.insets = new Insets(10, 0, 0, 0);
+
+		registryTable.getColumnModel().getColumn(0).setPreferredWidth(20);
+		registryTable.setAutoResizeMode(AUTO_RESIZE_LAST_COLUMN);
+		registryTable.setSelectionMode(SINGLE_SELECTION);
+		JScrollPane scrollPane = new JScrollPane(registryTable);
+		// registryTable.setFillsViewportHeight(true);
+
+		gbc.weighty = 1.0;
+		gbc.fill = BOTH;
+
+		add(scrollPane, gbc);
+
+		// Add buttons panel
+		gbc.gridx = 0;
+		gbc.gridy++;
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridwidth = 1;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = CENTER;
+		gbc.insets = new Insets(10, 0, 0, 0);
+		add(createRegistryButtonPanel(), gbc);
+
+		// Add buttons panel
+		gbc.gridx = 0;
+		gbc.gridy++;
+		gbc.weightx = 0.0;
+		gbc.weighty = 0.0;
+		gbc.gridwidth = 1;
+		gbc.fill = HORIZONTAL;
+		gbc.anchor = CENTER;
+		gbc.insets = new Insets(10, 0, 0, 0);
+		add(createButtonPanel(), gbc);
+
+		setFields();
+	}
+
+	/**
+	 * Create the buttons for managing the list of registries.
+	 * @return
+	 */
+	@SuppressWarnings("serial")
+	private Component createRegistryButtonPanel() {
+		JPanel panel = new JPanel();
+		panel.add(new DeselectingButton(new AbstractAction(REMOVE_LABEL) {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				remove();
+			}
+		}));
+		panel.add(new DeselectingButton(new AbstractAction(ADD_LOCAL_LABEL) {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				addLocal();
+			}
+		}));
+		panel.add(new DeselectingButton(new AbstractAction(ADD_REMOTE_LABEL) {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				addRemote();
+			}
+		}));
+		return panel;
+	}
+
+	/**
+	 * Create the panel to contain the buttons
+	 * 
+	 * @return
+	 */
+	@SuppressWarnings("serial")
+	private JPanel createButtonPanel() {
+		final JPanel panel = new JPanel();
+		panel.add(new DeselectingButton(new AbstractAction(HELP_LABEL) {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				Helper.showHelp(panel);
+			}
+		}));
+		panel.add(new DeselectingButton(new AbstractAction(RESET_LABEL) {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				setFields();
+			}
+		}));
+		panel.add(new DeselectingButton(new AbstractAction(APPLY_LABEL) {
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				applySettings();
+				setFields();
+			}
+		}));
+		return panel;
+	}
+
+	void remove() {
+		int selectedRow = registryTable.getSelectedRow();
+		if (selectedRow != -1)
+			tableModel.removeRow(selectedRow);
+	}
+
+	void addLocal() {
+		// Run the GUI
+		LocalRegistryPanel inputPanel = new LocalRegistryPanel();
+		ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+				ADD_LOCAL_TITLE, inputPanel);
+		vuid.addTextComponentValidation(inputPanel.getRegistryNameField(),
+				VALIDATION_MESSAGE, tableModel.getRegistryMap().keySet(),
+				DUPLICATE, "[\\p{L}\\p{Digit}_.]+", INVALID_NAME);
+		vuid.setSize(new Dimension(400, 250));
+		if (!vuid.show(ComponentPreferencePanel.this))
+			return;
+
+		// Add the local registry
+		String location = inputPanel.getLocationField().getText();
+		File newDir = new File(location);
+		try {
+			tableModel.insertRegistry(inputPanel.getRegistryNameField()
+					.getText(), getLocalRegistry(newDir));
+		} catch (MalformedURLException e) {
+			logger.error("bad url provided by user", e);
+			showMessageDialog(null, EXCEPTION_MESSAGE + location,
+					EXCEPTION_TITLE, ERROR_MESSAGE);
+		} catch (ComponentException e) {
+			logger.error("problem creating local registry", e);
+			showMessageDialog(null, EXCEPTION_MESSAGE + location,
+					EXCEPTION_TITLE, ERROR_MESSAGE);
+		}
+	}
+
+	void addRemote() {
+		RemoteRegistryPanel inputPanel = new RemoteRegistryPanel();
+		ValidatingUserInputDialog vuid = new ValidatingUserInputDialog(
+				ADD_REMOTE_TITLE, inputPanel);
+		vuid.addTextComponentValidation(inputPanel.getRegistryNameField(),
+				VALIDATION_MESSAGE, tableModel.getRegistryMap().keySet(),
+				DUPLICATE, "[\\p{L}\\p{Digit}_.]+", INVALID_NAME);
+		vuid.addTextComponentValidation(inputPanel.getLocationField(),
+				SET_URL_MESSAGE, null, "", URL_PATTERN, BAD_URL_MESSAGE);
+		vuid.setSize(new Dimension(400, 250));
+		if (!vuid.show(ComponentPreferencePanel.this))
+			return;
+
+		String location = inputPanel.getLocationField().getText();
+		try {
+			tableModel.insertRegistry(inputPanel.getRegistryNameField()
+					.getText(), getRemoteRegistry(location));
+		} catch (MalformedURLException e) {
+			logger.error("bad url provided by user", e);
+			showMessageDialog(null, EXCEPTION_MESSAGE + location,
+					EXCEPTION_TITLE, ERROR_MESSAGE);
+		} catch (ComponentException e) {
+			showMessageDialog(null, EXCEPTION_MESSAGE + location,
+					EXCEPTION_TITLE, ERROR_MESSAGE);
+			logger.error("problem creating remote registry", e);
+		}
+	}
+
+	Registry getLocalRegistry(File location) throws ComponentException,
+			MalformedURLException {
+		return factory.getRegistry(location.toURI().toURL());
+	}
+
+	Registry getRemoteRegistry(String location) throws MalformedURLException,
+			ComponentException {
+		URL url = new URL(location);
+		if (url.getProtocol() == null || url.getProtocol().equals("file"))
+			throw new MalformedURLException(
+					"may not use relative or local URLs for locating registry");
+		return factory.getRegistry(url);
+	}
+
+	private void applySettings() {
+		prefs.setRegistryMap(tableModel.getRegistryMap());
+	}
+
+	private void setFields() {
+		tableModel.setRegistryMap(prefs.getRegistryMap());
+	}
+}

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/preference/ComponentPreferenceUIFactory.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreferenceUIFactory.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreferenceUIFactory.java
new file mode 100644
index 0000000..03ae030
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/ComponentPreferenceUIFactory.java
@@ -0,0 +1,61 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.preference;
+
+import javax.swing.JPanel;
+import org.apache.taverna.configuration.Configurable;
+import org.apache.taverna.configuration.ConfigurationUIFactory;
+
+/**
+ * @author alanrw
+ */
+public class ComponentPreferenceUIFactory implements ConfigurationUIFactory {
+	public static final String DISPLAY_NAME = "Components";
+
+	private JPanel configPanel;//FIXME beaninject
+	private ComponentPreference prefs;// FIXME beaninject
+
+	public ComponentPreferenceUIFactory() {
+		super();
+	}
+
+	public void setConfigPanel(JPanel configPanel) {
+		this.configPanel = configPanel;
+	}
+
+	public void setPreferences(ComponentPreference pref) {
+		this.prefs = pref;
+	}
+
+	@Override
+	public boolean canHandle(String uuid) {
+		return uuid.equals(prefs.getUUID());
+	}
+
+	@Override
+	public Configurable getConfigurable() {
+		return prefs;
+	}
+
+	@Override
+	public JPanel getConfigurationPanel() {
+		return configPanel;
+	}
+}

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/preference/LocalRegistryPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/LocalRegistryPanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/LocalRegistryPanel.java
new file mode 100644
index 0000000..b9ba05e
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/LocalRegistryPanel.java
@@ -0,0 +1,132 @@
+/*
+* 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.preference;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JFileChooser.DIRECTORIES_ONLY;
+import static org.apache.log4j.Logger.getLogger;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+
+import javax.swing.AbstractAction;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+import org.apache.log4j.Logger;
+import org.apache.taverna.lang.ui.DeselectingButton;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class LocalRegistryPanel extends JPanel {
+	private static final String BROWSE_LABEL = "Browse";
+	private static final String LOCATION_LABEL = "Location:";
+	private static final String NAME_LABEL = "Name:";
+	private static final long serialVersionUID = 732945735813617327L;
+
+	private final Logger logger = getLogger(LocalRegistryPanel.class);
+
+	private JTextField registryNameField = new JTextField(20);
+	private JTextField locationField = new JTextField(20);
+
+	public LocalRegistryPanel() {
+		super(new GridBagLayout());
+
+		setBorder(new EmptyBorder(10, 10, 10, 10));
+
+		GridBagConstraints constraints = new GridBagConstraints();
+
+		constraints.anchor = WEST;
+		constraints.gridx = 0;
+		constraints.gridy = 0;
+		constraints.ipadx = 20;
+		add(new JLabel(NAME_LABEL), constraints);
+
+		constraints.gridx = 1;
+		constraints.gridwidth = 2;
+		constraints.ipadx = 0;
+		constraints.weightx = 1d;
+		constraints.fill = HORIZONTAL;
+		add(registryNameField, constraints);
+
+		constraints.gridy++;
+		constraints.gridx = 0;
+		constraints.ipadx = 20;
+		constraints.fill = NONE;
+		add(new JLabel(LOCATION_LABEL), constraints);
+
+		constraints.gridx = 1;
+		constraints.gridwidth = 2;
+		constraints.ipadx = 0;
+		constraints.weightx = 1d;
+		constraints.fill = HORIZONTAL;
+		add(locationField, constraints);
+
+		constraints.gridy++;
+		constraints.gridx = 0;
+		constraints.ipadx = 20;
+		constraints.fill = NONE;
+		add(new DeselectingButton(new AbstractAction(BROWSE_LABEL) {
+			private static final long serialVersionUID = -8676803966947261009L;
+
+			@Override
+			public void actionPerformed(ActionEvent arg0) {
+				pickDirectory();
+			}
+		}), constraints);
+	}
+
+	private void pickDirectory() {
+		JFileChooser chooser = new JFileChooser();
+		chooser.setFileSelectionMode(DIRECTORIES_ONLY);
+		int returnVal = chooser.showOpenDialog(LocalRegistryPanel.this);
+		try {
+			if (returnVal == APPROVE_OPTION)
+				locationField.setText(chooser.getSelectedFile()
+						.getCanonicalPath());
+		} catch (IOException e) {
+			logger.error("unexpected filesystem problem", e);
+		}
+	}
+
+	/**
+	 * @return the registryNameField
+	 */
+	public JTextField getRegistryNameField() {
+		return registryNameField;
+	}
+
+	/**
+	 * @return the locationField
+	 */
+	public JTextField getLocationField() {
+		return locationField;
+	}
+}

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/preference/RegistryTableModel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/RegistryTableModel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/RegistryTableModel.java
new file mode 100644
index 0000000..c3b5f04
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/RegistryTableModel.java
@@ -0,0 +1,86 @@
+/*
+* 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.preference;
+
+import static java.lang.String.format;
+
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.swing.table.DefaultTableModel;
+
+import io.github.taverna_extras.component.api.Registry;
+
+/**
+ * @author alanrw
+ * 
+ */
+public class RegistryTableModel extends DefaultTableModel {
+	private static final long serialVersionUID = -7789666945764974370L;
+	private SortedMap<String, Registry> registryMap = new TreeMap<String, Registry>();
+
+	public RegistryTableModel() {
+		super(new String[] { "Registry name", "Registry location" }, 0);
+	}
+
+	public void setRegistryMap(SortedMap<String, Registry> registries) {
+		registryMap.clear();
+		registryMap.putAll(registries);
+		updateRows();
+	}
+
+	public void updateRows() {
+		super.setRowCount(0);
+		for (Entry<String, Registry> entry : registryMap.entrySet())
+			super.addRow(new Object[] { entry.getKey(),
+					entry.getValue().getRegistryBaseString() });
+	}
+
+	@Override
+	public boolean isCellEditable(int row, int column) {
+		return false;
+	}
+	
+    public String getRowTooltipText(int row) {
+        Registry registry = registryMap.get(getValueAt(row, 0));
+        return format("This is a %s registry.", registry.getRegistryTypeName());
+    }
+
+	@Override
+	public void removeRow(int row) {
+		String key = (String) getValueAt(row, 0);
+		registryMap.remove(key);
+		super.removeRow(row);
+	}
+
+	public void insertRegistry(String name, Registry newRegistry) {
+		registryMap.put(name, newRegistry);
+		updateRows();
+	}
+
+	/**
+	 * @return the registryMap
+	 */
+	public SortedMap<String, Registry> getRegistryMap() {
+		return registryMap;
+	}
+
+}

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/preference/RemoteRegistryPanel.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/RemoteRegistryPanel.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/RemoteRegistryPanel.java
new file mode 100644
index 0000000..1f98933
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/preference/RemoteRegistryPanel.java
@@ -0,0 +1,94 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package io.github.taverna_extras.component.ui.preference;
+
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static java.awt.GridBagConstraints.WEST;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * @author alanrw
+ *
+ */
+public class RemoteRegistryPanel extends JPanel {
+	private static final String LOCATION_LABEL = "Location:";
+	private static final String NAME_LABEL = "Name:";
+	private static final long serialVersionUID = 8833815753329010062L;
+
+	private JTextField registryNameField = new JTextField(20);
+	private JTextField locationField = new JTextField(20);
+	
+	public RemoteRegistryPanel() {
+		super(new GridBagLayout());
+
+		setBorder(new EmptyBorder(10, 10, 10, 10));
+		
+		GridBagConstraints constraints = new GridBagConstraints();
+
+		constraints.anchor = WEST;
+		constraints.gridx = 0;
+		constraints.gridy = 0;
+		constraints.ipadx = 20;
+		add(new JLabel(NAME_LABEL), constraints);
+
+		constraints.gridx = 1;
+		constraints.gridwidth = 2;
+		constraints.ipadx = 0;
+		constraints.weightx = 1d;
+		constraints.fill = HORIZONTAL;
+		add(registryNameField, constraints);
+		
+		constraints.gridy++;
+		constraints.gridx = 0;
+		constraints.ipadx = 20;
+		constraints.fill = NONE;
+		add(new JLabel(LOCATION_LABEL), constraints);
+		
+		constraints.gridx = 1;
+		constraints.gridwidth = 2;
+		constraints.ipadx = 0;
+		constraints.weightx = 1d;
+		constraints.fill = HORIZONTAL;
+		add(locationField, constraints);
+	}
+
+	/**
+	 * @return the registryNameField
+	 */
+	public JTextField getRegistryNameField() {
+		return registryNameField;
+	}
+
+	/**
+	 * @return the locationField
+	 */
+	public JTextField getLocationField() {
+		return locationField;
+	}
+
+}

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/serviceprovider/ComponentServiceDesc.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceDesc.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceDesc.java
new file mode 100644
index 0000000..03b2eda
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceDesc.java
@@ -0,0 +1,174 @@
+/*
+* 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.serviceprovider;
+
+import static java.util.Arrays.asList;
+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.ComponentConstants.ACTIVITY_URI;
+
+import java.net.URI;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.Icon;
+
+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 io.github.taverna_extras.component.ui.preference.ComponentPreference;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.configurations.Configuration;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.taverna.servicedescriptions.ServiceDescription;
+
+public class ComponentServiceDesc extends ServiceDescription {
+	private static Logger logger = getLogger(ComponentServiceDesc.class);
+
+	private Version.ID identification;
+	private final ComponentPreference preference;
+	private final ComponentFactory factory;
+	private final ComponentServiceIcon iconProvider;
+
+	public ComponentServiceDesc(ComponentPreference preference,
+			ComponentFactory factory, ComponentServiceIcon iconProvider,
+			Version.ID identification) {
+		this.preference = preference;
+		this.factory = factory;
+		this.identification = identification;
+		this.iconProvider = iconProvider;
+	}
+
+	/**
+	 * The configuration bean which is to be used for configuring the
+	 * instantiated activity. This is built from the component identifier.
+	 */
+	@Override
+	public Configuration getActivityConfiguration() {
+		Configuration config = new Configuration();
+		installActivityConfiguration(config);
+		return config;
+	}
+
+	/**
+	 * Make the given activity be configured to be using the component that this
+	 * class identifies.
+	 */
+	public void installActivityConfiguration(Activity activity) {
+		installActivityConfiguration(activity.getConfiguration());
+	}
+
+	/**
+	 * Update the given configuration to have the fields for the component that
+	 * this class identifies.
+	 */
+	public void installActivityConfiguration(Configuration config) {
+		ObjectNode c = config.getJsonAsObjectNode();
+		ID id = getIdentification();
+		c.put(REGISTRY_BASE, id.getRegistryBase().toExternalForm());
+		c.put(FAMILY_NAME, id.getFamilyName());
+		c.put(COMPONENT_NAME, id.getComponentName());
+		c.put(COMPONENT_VERSION, id.getComponentVersion());
+		config.setJson(c);
+	}
+
+	/**
+	 * An icon to represent this service description in the service palette.
+	 */
+	@Override
+	public Icon getIcon() {
+		return iconProvider.getIcon();
+	}
+
+	/**
+	 * The display name that will be shown in service palette and will be used
+	 * as a template for processor name when added to workflow.
+	 */
+	@Override
+	public String getName() {
+		return getIdentification().getComponentName();
+	}
+
+	/**
+	 * The path to this service description in the service palette. Folders will
+	 * be created for each element of the returned path.
+	 */
+	@Override
+	public List<String> getPath() {
+		return asList("Components",
+				preference.getRegistryName(identification.getRegistryBase()),
+				identification.getFamilyName());
+	}
+
+	/**
+	 * Returns a list of data values uniquely identifying this component
+	 * description (i.e., no duplicates).
+	 */
+	@Override
+	protected List<? extends Object> getIdentifyingData() {
+		return Arrays.asList(identification.getRegistryBase(),
+				identification.getFamilyName(),
+				identification.getComponentName());
+	}
+
+	@Override
+	public String toString() {
+		return "Component " + getName();
+	}
+
+	/**
+	 * @return the identification
+	 */
+	public Version.ID getIdentification() {
+		return identification;
+	}
+
+	/**
+	 * @param identification
+	 *            the identification to set
+	 */
+	public void setIdentification(Version.ID identification) {
+		this.identification = identification;
+	}
+	
+	public URL getHelpURL() {
+		try {
+			return factory.getVersion(getIdentification()).getHelpURL();
+		} catch (ComponentException e) {
+			logger.error(
+					"failed to get component in order to determine its help URL",
+					e);
+			return null;
+		}
+	}
+
+	@Override
+	public URI getActivityType() {
+		return ACTIVITY_URI;
+	}
+}

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/serviceprovider/ComponentServiceIcon.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceIcon.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceIcon.java
new file mode 100644
index 0000000..16e0d5f
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceIcon.java
@@ -0,0 +1,51 @@
+/*
+* 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.serviceprovider;
+
+import static io.github.taverna_extras.component.ui.serviceprovider.Service.COMPONENT_ACTIVITY_URI;
+
+import java.net.URI;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import org.apache.taverna.workbench.activityicons.ActivityIconSPI;
+
+public class ComponentServiceIcon implements ActivityIconSPI {
+	private static class Init {
+		private static Icon icon = new ImageIcon(
+				ComponentServiceIcon.class.getResource("/brick.png"));
+	}
+
+	@Override
+	public int canProvideIconScore(URI activityType) {
+		if (activityType.equals(COMPONENT_ACTIVITY_URI))
+			return DEFAULT_ICON + 1;
+		return NO_ICON;
+	}
+
+	@Override
+	public Icon getIcon(URI activityType) {
+		return Init.icon;
+	}
+
+	public Icon getIcon() {
+		return Init.icon;
+	}
+}

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/serviceprovider/ComponentServiceProvider.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceProvider.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceProvider.java
new file mode 100644
index 0000000..d5350d0
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceProvider.java
@@ -0,0 +1,236 @@
+/*
+* 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.serviceprovider;
+
+import static java.util.Arrays.asList;
+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.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.ComponentConstants.ACTIVITY_URI;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+
+import javax.swing.Icon;
+
+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.ui.panel.RegistryAndFamilyChooserPanel;
+import io.github.taverna_extras.component.ui.preference.ComponentPreference;
+import io.github.taverna_extras.component.ui.util.Utils;
+
+import org.apache.taverna.scufl2.api.common.Visitor;
+import org.apache.taverna.scufl2.api.configurations.Configuration;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.taverna.servicedescriptions.AbstractConfigurableServiceProvider;
+import org.apache.taverna.servicedescriptions.CustomizedConfigurePanelProvider;
+import org.apache.taverna.servicedescriptions.ServiceDescriptionProvider;
+
+public class ComponentServiceProvider extends
+		AbstractConfigurableServiceProvider implements
+		CustomizedConfigurePanelProvider {
+	static final URI providerId = URI
+			.create("http://taverna.sf.net/2012/service-provider/component");
+	private static Logger logger = getLogger(ComponentServiceProvider.class);
+
+	private final ComponentFactory factory;
+	private final ComponentPreference prefs;
+	private final ComponentServiceIcon iconProvider;
+	private final Utils utils;
+
+	public ComponentServiceProvider(ComponentFactory factory,
+			ComponentPreference prefs, ComponentServiceIcon iconProvider,
+			Utils utils) {
+		super(makeConfig(null, null));
+		this.factory = factory;
+		this.prefs = prefs;
+		this.iconProvider = iconProvider;
+		this.utils = utils;
+	}
+
+	private static class Conf {
+		URL registryBase;
+		String familyName;
+
+		Conf(Configuration config) throws MalformedURLException  {
+			ObjectNode node = config.getJsonAsObjectNode();
+			JsonNode item = node.get(REGISTRY_BASE);
+			if (item != null && !item.isNull())
+				registryBase = URI.create(item.textValue()).toURL();
+			item = node.get(FAMILY_NAME);
+			if (item != null && !item.isNull())
+				familyName = item.textValue();
+		}
+	}
+
+	/**
+	 * Do the actual search for services. Return using the callBack parameter.
+	 */
+	@Override
+	public void findServiceDescriptionsAsync(
+			FindServiceDescriptionsCallBack callBack) {
+		Conf config;
+
+		Registry registry;
+		try {
+			config = new Conf(getConfiguration());
+			registry = factory.getRegistry(config.registryBase);
+		} catch (ComponentException | MalformedURLException e) {
+			logger.error("failed to get registry API", e);
+			callBack.fail("Unable to read components", e);
+			return;
+		}
+
+		try {
+			List<ComponentServiceDesc> results = new ArrayList<>();
+			for (Family family : registry.getComponentFamilies()) {
+				// TODO get check on family name in there
+				if (family.getName().equals(config.familyName))
+					for (Component component : family.getComponents())
+						try {
+							SortedMap<Integer, Version> versions = component
+									.getComponentVersionMap();
+							ComponentServiceDesc newDesc = new ComponentServiceDesc(
+									prefs, factory, iconProvider, versions.get(
+											versions.lastKey()).getID());
+							results.add(newDesc);
+						} catch (Exception e) {
+							logger.error("problem getting service descriptor",
+									e);
+						}
+				callBack.partialResults(results);
+				callBack.finished();
+			}
+		} catch (ComponentException e) {
+			logger.error("problem querying registry", e);
+			callBack.fail("Unable to read components", e);
+		}
+	}
+
+	/**
+	 * Icon for service provider
+	 */
+	@Override
+	public Icon getIcon() {
+		return iconProvider.getIcon();
+	}
+
+	/**
+	 * Name of service provider, appears in right click for 'Remove service
+	 * provider'
+	 */
+	@Override
+	public String getName() {
+		return "Component service";
+	}
+
+	@Override
+	public String toString() {
+		return getName();
+	}
+
+	@Override
+	public String getId() {
+		return providerId.toASCIIString();
+	}
+
+	@Override
+	protected List<? extends Object> getIdentifyingData() {
+		try {
+			Conf config = new Conf(getConfiguration());
+			return asList(config.registryBase.toString(), config.familyName);
+		} catch (MalformedURLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override
+	public void createCustomizedConfigurePanel(
+			CustomizedConfigureCallBack callBack) {
+		RegistryAndFamilyChooserPanel panel = new RegistryAndFamilyChooserPanel(prefs);
+
+		if (showConfirmDialog(null, panel, "Component family import",
+				OK_CANCEL_OPTION) != OK_OPTION)
+			return;
+
+		Registry registry = panel.getChosenRegistry();
+		Family family = panel.getChosenFamily();
+		if (registry == null || family == null)
+			return;
+		callBack.newProviderConfiguration(makeConfig(
+				registry.getRegistryBaseString(), family.getName()));
+	}
+
+	private static Configuration makeConfig(String registryUrl,
+			String familyName) {
+		ObjectNode cfg = JsonNodeFactory.instance.objectNode();
+		cfg.put(REGISTRY_BASE, registryUrl);
+		cfg.put(FAMILY_NAME, familyName);
+		Configuration conf = new Configuration();
+		conf.setJson(cfg);
+		conf.setType(providerId);
+		return conf;
+	}
+
+	@Override
+	public ServiceDescriptionProvider newInstance() {
+		return new ComponentServiceProvider(factory, prefs, iconProvider, utils);
+	}
+
+	@Override
+	public URI getType() {
+		return ACTIVITY_URI;
+	}
+
+	@Override
+	public void setType(URI type) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public boolean accept(Visitor visitor) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+	public void refreshProvidedComponent(Version.ID ident) {
+		try {
+			utils.refreshComponentServiceProvider(new ComponentServiceProviderConfig(
+					ident).getConfiguration());
+		} catch (Exception e) {
+			logger.error("Unable to refresh service panel", e);
+		}
+	}
+}

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/serviceprovider/ComponentServiceProviderConfig.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceProviderConfig.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceProviderConfig.java
new file mode 100644
index 0000000..72b7458
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/ComponentServiceProviderConfig.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.serviceprovider;
+
+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.serviceprovider.ComponentServiceProvider.providerId;
+
+import java.net.URL;
+
+import io.github.taverna_extras.component.api.Family;
+import io.github.taverna_extras.component.api.Version;
+
+import org.apache.taverna.scufl2.api.configurations.Configuration;
+
+public class ComponentServiceProviderConfig {
+	private URL registryBase;
+	private String familyName;
+
+	public ComponentServiceProviderConfig() {
+	}
+
+	public ComponentServiceProviderConfig(Family family) {
+		registryBase = family.getComponentRegistry().getRegistryBase();
+		familyName = family.getName();
+	}
+
+	public ComponentServiceProviderConfig(Version.ID ident) {
+		registryBase = ident.getRegistryBase();
+		familyName = ident.getFamilyName();
+	}
+
+	/**
+	 * @return the registryBase
+	 */
+	public URL getRegistryBase() {
+		return registryBase;
+	}
+
+	/**
+	 * @param registryBase
+	 *            the registryBase to set
+	 */
+	public void setRegistryBase(URL registryBase) {
+		this.registryBase = registryBase;
+	}
+
+	/**
+	 * @return the familyName
+	 */
+	public String getFamilyName() {
+		return familyName;
+	}
+
+	/**
+	 * @param familyName
+	 *            the familyName to set
+	 */
+	public void setFamilyName(String familyName) {
+		this.familyName = familyName;
+	}
+
+	public Configuration getConfiguration() {
+		Configuration c = new Configuration();
+		c.getJsonAsObjectNode().put(REGISTRY_BASE,
+				registryBase.toExternalForm());
+		c.getJsonAsObjectNode().put(FAMILY_NAME, familyName);
+		c.setType(providerId);
+		return c;
+	}
+}

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/serviceprovider/Service.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/Service.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/Service.java
new file mode 100644
index 0000000..32c606e
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/serviceprovider/Service.java
@@ -0,0 +1,27 @@
+/*
+* 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.serviceprovider;
+
+import java.net.URI;
+
+public interface Service {
+	URI COMPONENT_ACTIVITY_URI = URI
+			.create("http://ns.taverna.org.uk/2010/activity/component");
+}

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/util/ComponentFileType.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/util/ComponentFileType.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/util/ComponentFileType.java
new file mode 100644
index 0000000..ba4801e
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/util/ComponentFileType.java
@@ -0,0 +1,51 @@
+/*
+* 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.util;
+
+import org.apache.taverna.workbench.file.FileType;
+
+/**
+ * The type of components.
+ * 
+ * @author alanrw
+ */
+public class ComponentFileType extends FileType {
+	// TODO Change mimetype for sculf2?
+	static final String COMPONENT_MIMETYPE = "application/vnd.taverna.component";
+
+	private ComponentFileType() {
+	}
+
+	@Override
+	public String getDescription() {
+		return "Taverna component";
+	}
+
+	// Not really used
+	@Override
+	public String getExtension() {
+		return "component";
+	}
+
+	@Override
+	public String getMimeType() {
+		return COMPONENT_MIMETYPE;
+	}
+}

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/util/ComponentHealthCheck.java
----------------------------------------------------------------------
diff --git a/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/util/ComponentHealthCheck.java b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/util/ComponentHealthCheck.java
new file mode 100644
index 0000000..a3585f4
--- /dev/null
+++ b/taverna-component-activity-ui/src/main/java/io/github/taverna_extras/component/ui/util/ComponentHealthCheck.java
@@ -0,0 +1,86 @@
+/*
+* 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.util;
+
+import static org.apache.log4j.Logger.getLogger;
+
+import java.util.ArrayList;
+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.ui.ComponentActivityConfigurationBean;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.common.Visitor;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.validation.correctness.DefaultDispatchingVisitor;
+import org.apache.taverna.visit.VisitKind;
+import org.apache.taverna.visit.VisitReport;
+
+public class ComponentHealthCheck extends VisitKind {
+	public static final int NO_PROBLEM = 0;
+	public static final int OUT_OF_DATE = 10;
+	public static final int NON_SHAREABLE = 20;
+	public static final int FAILS_PROFILE = 30;
+	private static Logger logger = getLogger(ComponentHealthCheck.class);
+	private static final String OUTDATED_MSG = "Component out of date";
+
+	private ComponentFactory factory;
+
+	public void setComponentFactory(ComponentFactory factory) {
+		this.factory = factory;
+	}
+
+	public List<Object> checkForOutdatedComponents(WorkflowBundle bundle) {
+		UpgradeChecker uc = new UpgradeChecker();
+		bundle.accept(uc);
+		return uc.warnings;
+	}
+
+	private class UpgradeChecker extends DefaultDispatchingVisitor {
+		ComponentFactory factory;
+		List<Object> warnings = new ArrayList<>();
+
+		@Override
+		public void visitActivity(Activity activity) {
+			ComponentActivityConfigurationBean config = new ComponentActivityConfigurationBean(
+					activity.getConfiguration().getJson(), factory);
+			Version v;
+			try {
+				v = config.getVersion();
+			} catch (ComponentException e) {
+				logger.error("failed to get component description", e);
+				warnings.add(e);//FIXME Just putting the exception in here isn't good
+				return;
+			}
+			visitComponent(activity, v);
+		}
+		protected void visitComponent(Activity activity, Version version) {
+			int latest = version.getComponent().getComponentVersionMap().lastKey();
+			if (latest > version.getVersionNumber())
+				warnings.add(new VisitReport(ComponentHealthCheck.this,
+						activity, OUTDATED_MSG, OUT_OF_DATE,
+						VisitReport.Status.WARNING));
+		}
+	}
+}